From 38e40be12037bb20434956d60ced2d2c074c93a4 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 18 Feb 2026 22:58:29 +0100 Subject: [PATCH 1/3] refactor(tests): leftover manual gas calculation/hard-coded gas costs (#2222) --- .../tools/tools_code/generators.py | 7 +- .../eip3860_initcode/test_initcode.py | 95 +++++++------------ 2 files changed, 41 insertions(+), 61 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 99434da8ce..c5b8c9eae2 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -188,7 +188,12 @@ def __new__( res += ( Op.SWAP1 + Op.SUB - + Op.PUSH1(overhead_cost + 2) + + Op.PUSH1[overhead_cost] + + Op.GAS + + Op.GAS + + Op.SWAP1 + + Op.SUB + + Op.ADD + Op.SWAP1 + Op.SSTORE(sstore_key, Op.SUB) ) diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index 1f66635377..48fbf96108 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -24,7 +24,6 @@ Transaction, TransactionException, TransactionReceipt, - ceiling_division, compute_create_address, ) @@ -141,11 +140,9 @@ def test_contract_creating_tx( ) tx = Transaction( - nonce=0, to=None, data=initcode, - gas_limit=10000000, - gas_price=10, + gas_limit=10_000_000, sender=sender, ) @@ -320,12 +317,10 @@ def tx( pytest.fail("Invalid gas test case provided.") return Transaction( - nonce=0, to=None, access_list=tx_access_list, data=initcode, gas_limit=gas_limit, - gas_price=10, error=tx_error, sender=sender, # The entire gas limit is expected to be consumed. @@ -415,29 +410,52 @@ def create2_salt(self) -> int: return 0xDEADBEEF @pytest.fixture - def creator_code(self, opcode: Op, create2_salt: int) -> Bytecode: + def create_code( + self, opcode: Op, create2_salt: int, initcode: Initcode + ) -> Bytecode: + """ + Generate the CREATE/CREATE2 bytecode. + """ + return ( + opcode( + size=Op.CALLDATASIZE, + salt=create2_salt, + init_code_size=len(initcode), + ) + if opcode == Op.CREATE2 + else opcode(size=Op.CALLDATASIZE, init_code_size=len(initcode)) + ) + + @pytest.fixture + def creator_code(self, fork: Fork, create_code: Bytecode) -> Bytecode: """ Generate code for the creator contract which calls CREATE/CREATE2. """ return ( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.GAS - + ( - opcode(size=Op.CALLDATASIZE, salt=create2_salt) - if opcode == Op.CREATE2 - else opcode(size=Op.CALLDATASIZE) - ) + + create_code + Op.GAS # stack: [Gas 2, Call Result, Gas 1] + Op.SWAP1 # stack: [Call Result, Gas 2, Gas 1] - + Op.SSTORE(0, unchecked=True) + + Op.PUSH1[0] + # stack: [0, Call Result, Gas 2, Gas 1] + + Op.SSTORE # stack: [Gas 2, Gas 1] + Op.SWAP1 # stack: [Gas 1, Gas 2] + Op.SUB # stack: [Gas 1 - Gas 2] - + Op.SSTORE(1, unchecked=True) + + Op.PUSH1[Op.GAS.gas_cost(fork)] + # stack: [Op.GAS cost, Gas 1 - Gas 2] + + Op.SWAP1 + # stack: [Gas 1 - Gas 2, Op.GAS cost] + + Op.SUB + # stack: [Gas 1 - Gas 2 - Op.GAS cost] + + Op.PUSH1[1] + # stack: [1, Gas 1 - Gas 2 - Op.GAS cost] + + Op.SSTORE ) @pytest.fixture @@ -491,45 +509,12 @@ def tx( ) -> Transaction: """Generate transaction that executes the caller contract.""" return Transaction( - nonce=0, to=caller_contract_address, data=initcode, - gas_limit=10000000, - gas_price=10, + gas_limit=10_000_000, sender=sender, ) - @pytest.fixture - def contract_creation_gas_cost( - self, fork: Fork, opcode: Op, create2_salt: int - ) -> int: - """Calculate gas cost of the contract creation operation.""" - create_code = ( - opcode(size=Op.CALLDATASIZE, salt=create2_salt) - if opcode == Op.CREATE2 - else opcode(size=Op.CALLDATASIZE) - ) - return (create_code + Op.GAS).gas_cost(fork) - - @pytest.fixture - def initcode_word_cost(self, fork: Fork, initcode: Initcode) -> int: - """Calculate gas cost charged for the initcode length.""" - gas_costs = fork.gas_costs() - return ceiling_division(len(initcode), 32) * gas_costs.G_INITCODE_WORD - - @pytest.fixture - def create2_word_cost( - self, opcode: Op, fork: Fork, initcode: Initcode - ) -> int: - """Calculate gas cost charged for the initcode length.""" - if opcode == Op.CREATE: - return 0 - - gas_costs = fork.gas_costs() - return ( - ceiling_division(len(initcode), 32) * gas_costs.G_KECCAK_256_WORD - ) - @pytest.mark.xdist_group(name="bigmem") @pytest.mark.slow() def test_create_opcode_initcode( @@ -543,9 +528,7 @@ def test_create_opcode_initcode( caller_contract_address: Address, creator_contract_address: Address, created_contract_address: Address, - contract_creation_gas_cost: int, - initcode_word_cost: int, - create2_word_cost: int, + create_code: Bytecode, fork: Fork, ) -> None: """ @@ -574,18 +557,10 @@ def test_create_opcode_initcode( ) else: - expected_gas_usage = contract_creation_gas_cost + expected_gas_usage = create_code.gas_cost(fork) # The initcode is only executed if the length check succeeds expected_gas_usage += initcode.gas_cost(fork) - # CREATE2 hashing cost should only be deducted if the initcode - # does not exceed the max length - expected_gas_usage += create2_word_cost - - # Initcode word cost is only deducted if the length check - # succeeds - expected_gas_usage += initcode_word_cost - # Call returns 1 as valid initcode length s[0]==1 && s[1]==1 post[caller_contract_address] = Account( nonce=1, From fc58fdd10c3770b4fa805385650e7a5d2a765992 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:07:59 -0500 Subject: [PATCH 2/3] chore: add missing bpo forks to pyproject (#2233) --- pyproject.toml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 89e132cf73..37c871096f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,36 @@ packages = [ "ethereum.forks.osaka.vm.instructions", "ethereum.forks.osaka.vm.precompiled_contracts", "ethereum.forks.osaka.vm.precompiled_contracts.bls12_381", + "ethereum.forks.bpo1", + "ethereum.forks.bpo1.utils", + "ethereum.forks.bpo1.vm", + "ethereum.forks.bpo1.vm.instructions", + "ethereum.forks.bpo1.vm.precompiled_contracts", + "ethereum.forks.bpo1.vm.precompiled_contracts.bls12_381", + "ethereum.forks.bpo2", + "ethereum.forks.bpo2.utils", + "ethereum.forks.bpo2.vm", + "ethereum.forks.bpo2.vm.instructions", + "ethereum.forks.bpo2.vm.precompiled_contracts", + "ethereum.forks.bpo2.vm.precompiled_contracts.bls12_381", + "ethereum.forks.bpo3", + "ethereum.forks.bpo3.utils", + "ethereum.forks.bpo3.vm", + "ethereum.forks.bpo3.vm.instructions", + "ethereum.forks.bpo3.vm.precompiled_contracts", + "ethereum.forks.bpo3.vm.precompiled_contracts.bls12_381", + "ethereum.forks.bpo4", + "ethereum.forks.bpo4.utils", + "ethereum.forks.bpo4.vm", + "ethereum.forks.bpo4.vm.instructions", + "ethereum.forks.bpo4.vm.precompiled_contracts", + "ethereum.forks.bpo4.vm.precompiled_contracts.bls12_381", + "ethereum.forks.bpo5", + "ethereum.forks.bpo5.utils", + "ethereum.forks.bpo5.vm", + "ethereum.forks.bpo5.vm.instructions", + "ethereum.forks.bpo5.vm.precompiled_contracts", + "ethereum.forks.bpo5.vm.precompiled_contracts.bls12_381", "ethereum.forks.amsterdam", "ethereum.forks.amsterdam.block_access_lists", "ethereum.forks.amsterdam.utils", From 834e482a0ac39cced030cb7449fdb65b5d8479fc Mon Sep 17 00:00:00 2001 From: spencer Date: Wed, 18 Feb 2026 23:05:52 +0000 Subject: [PATCH 3/3] feat(test-client-clis): add stream-mode opcode count support for geth (#2170) * feat(client_clis): add stream-mode opcode count support for geth t8n * fix(test-types): strip geth blockTimestamp from transaction logs * fix(test-types): ignore extra geth fields in FixtureTransactionLog * fix(test-types): address review feedback for geth opcode count support Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../client_clis/clis/geth.py | 1 + .../client_clis/transition_tool.py | 25 +++++++++++++++++-- .../src/execution_testing/fixtures/common.py | 9 ++++++- .../test_types/receipt_types.py | 1 + 4 files changed, 33 insertions(+), 3 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 e2e2c439d6..0ac1be64d4 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -258,6 +258,7 @@ class GethTransitionTool(GethEvm, TransitionTool): subcommand: Optional[str] = "t8n" trace: bool t8n_use_stream = True + supports_opcode_count: ClassVar[bool] = True def __init__( self, 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 223a0cda71..074bbfd819 100644 --- a/packages/testing/src/execution_testing/client_clis/transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/transition_tool.py @@ -643,6 +643,23 @@ def _evaluate_stream( }, ) + if self.supports_opcode_count: + opcode_count_file_path = Path(temp_dir.name) / "opcodes.json" + if opcode_count_file_path.exists(): + opcode_count = OpcodeCount.model_validate_json( + opcode_count_file_path.read_text() + ) + output.result.opcode_count = opcode_count + + if debug_output_path: + with profiler.pause(): + dump_files_to_directory( + debug_output_path, + { + "opcodes.json": opcode_count.model_dump(), + }, + ) + if self.trace: output.result.traces = self.collect_traces( output.result.receipts, temp_dir, debug_output_path @@ -694,8 +711,12 @@ def safe_t8n_args( f"--state.reward={reward}", ] - if self.trace and temp_dir: - args.extend([trace_flag, f"--output.basedir={temp_dir.name}"]) + if temp_dir and (self.trace or self.supports_opcode_count): + args.append(f"--output.basedir={temp_dir.name}") + if self.trace: + args.append(trace_flag) + if self.supports_opcode_count and temp_dir: + args.extend(["--opcode.count", "opcodes.json"]) return args diff --git a/packages/testing/src/execution_testing/fixtures/common.py b/packages/testing/src/execution_testing/fixtures/common.py index 2dadf0127b..e56cb5a8b4 100644 --- a/packages/testing/src/execution_testing/fixtures/common.py +++ b/packages/testing/src/execution_testing/fixtures/common.py @@ -2,7 +2,12 @@ from typing import Any, ClassVar, Dict, List -from pydantic import AliasChoices, Field, computed_field, model_validator +from pydantic import ( + AliasChoices, + Field, + computed_field, + model_validator, +) from execution_testing.base_types import ( BlobSchedule, @@ -102,6 +107,8 @@ def sign(self) -> None: class FixtureTransactionLog(CamelModel, RLPSerializable): """Fixture variant of the TransactionLog type.""" + model_config = CamelModel.model_config | {"extra": "ignore"} + address: Address | None = None topics: List[Hash] | None = None data: Bytes | None = None diff --git a/packages/testing/src/execution_testing/test_types/receipt_types.py b/packages/testing/src/execution_testing/test_types/receipt_types.py index b4bbf9b95a..9fe5428fef 100644 --- a/packages/testing/src/execution_testing/test_types/receipt_types.py +++ b/packages/testing/src/execution_testing/test_types/receipt_types.py @@ -26,6 +26,7 @@ class TransactionLog(CamelModel): block_hash: Hash | None = None log_index: HexNumber | None = None removed: bool | None = None + block_timestamp: HexNumber | None = None class ReceiptDelegation(CamelModel):