From bd9384844cf3fef4ae28fd39209d7dc67590f8dc Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Wed, 13 Aug 2025 18:12:33 +0800 Subject: [PATCH 1/4] refactor(eip7825): update legacy configuration --- src/ethereum_test_forks/forks/forks.py | 2 +- .../eip7825_transaction_gas_limit_cap/spec.py | 26 +++++ .../test_tx_gas_limit_transition_fork.py | 101 ++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/osaka/eip7825_transaction_gas_limit_cap/spec.py create mode 100644 tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 207d4c623df..bb3297e3641 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -1457,7 +1457,7 @@ def full_blob_tx_wrapper_version(cls, block_number=0, timestamp=0) -> int | None @classmethod def transaction_gas_limit_cap(cls, block_number: int = 0, timestamp: int = 0) -> int | None: - """At Osaka, transaction gas limit is capped at 30 million.""" + """At Osaka, transaction gas limit is capped at 16 million (2**24).""" return 16_777_216 @classmethod diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py b/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py new file mode 100644 index 00000000000..05f459f1bce --- /dev/null +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py @@ -0,0 +1,26 @@ +"""Defines EIP-7825 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-7825 reference specification +ref_spec_7825 = ReferenceSpec("EIPS/eip-7825.md", "47cbfed315988c0bd4d10002c110ae402504cd94") + + +@dataclass(frozen=True) +class Spec: + """Constants and helpers for the EIP-7825 Transaction Gas Limit Cap tests.""" + + # Gas limit constants + tx_gas_limit_cap = 2**24 # 16,777,216 + + # Blob transaction constants + blob_commitment_version_kzg = 1 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 new file mode 100644 index 00000000000..59c5bc69045 --- /dev/null +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py @@ -0,0 +1,101 @@ +""" +abstract: Tests [EIP-7825 Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825) + Test cases for [EIP-7825 Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825)]. +""" + +import pytest + +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Transaction, + TransactionException, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .spec import Spec + + +@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True) +@pytest.mark.exception_test +def test_transaction_gas_limit_cap_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +): + """ + Test transaction gas limit cap behavior at the Osaka transition. + + Before timestamp 15000: No gas limit cap (transactions with gas > 2^24 are valid) + At/after timestamp 15000: Gas limit cap of 2^24 is enforced + """ + sender = pre.fund_eoa() + contract_address = pre.deploy_contract( + code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + Op.STOP, + ) + + pre_cap = fork.transaction_gas_limit_cap() + if pre_cap is None: + pre_cap = Spec.tx_gas_limit_cap + + # Transaction with gas limit above the cap before transition + high_gas_tx = Transaction( + ty=0, # Legacy transaction + to=contract_address, + gas_limit=pre_cap + 1, + data=b"", + value=0, + sender=sender, + ) + + post_cap = fork.transaction_gas_limit_cap(timestamp=15_000) + post_cap_tx_error = TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + + assert post_cap is not None, "Post cap should not be None" + assert post_cap <= pre_cap, ( + "Post cap should be less than or equal to pre cap, test needs update" + ) + + # Transaction with gas limit at the cap + cap_gas_tx = Transaction( + ty=0, # Legacy transaction + to=contract_address, + gas_limit=post_cap + 1, + data=b"", + value=0, + sender=sender, + error=post_cap_tx_error, + ) + + blocks = [] + + # Before transition (timestamp < 15000): high gas transaction should succeed + blocks.append( + Block( + timestamp=14_999, + txs=[high_gas_tx], + ) + ) + + # At transition (timestamp = 15000): high gas transaction should fail + blocks.append( + Block( + timestamp=15_000, + txs=[cap_gas_tx], # Only transaction at the cap succeeds + exception=post_cap_tx_error, + ) + ) + + # Post state: storage should be updated by successful transactions + post = { + contract_address: Account( + storage={ + 0: 1, # Set by first transaction (before transition) + } + ) + } + + blockchain_test(pre=pre, blocks=blocks, post=post) From a7d3e538455378d510f5246c8b6a59a13ca685ff Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Wed, 13 Aug 2025 18:13:47 +0800 Subject: [PATCH 2/4] tests(eip7825): add extra cases for tx gas limit --- .../test_tx_gas_limit.py | 375 +++++++++++++++--- 1 file changed, 311 insertions(+), 64 deletions(-) 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 e12138afde8..e8596e3df9f 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 @@ -7,8 +7,10 @@ import pytest +from ethereum_test_exceptions import BlockException from ethereum_test_forks import Fork from ethereum_test_tools import ( + AccessList, Account, Address, Alloc, @@ -16,6 +18,7 @@ Block, BlockchainTestFiller, Environment, + Hash, StateTestFiller, Storage, Transaction, @@ -25,11 +28,11 @@ from ethereum_test_tools.utility.pytest import ParameterSet from ethereum_test_tools.vm.opcode import Opcodes as Op -REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7825.md" -REFERENCE_SPEC_VERSION = "47cbfed315988c0bd4d10002c110ae402504cd94" +from .spec import Spec, ref_spec_7825 -TX_GAS_LIMIT = 2**24 # 16,777,216 -BLOB_COMMITMENT_VERSION_KZG = 1 +# Update reference spec constants +REFERENCE_SPEC_GIT_PATH = ref_spec_7825.git_path +REFERENCE_SPEC_VERSION = ref_spec_7825.version def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]: @@ -41,7 +44,7 @@ def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]: if fork_tx_gas_limit_cap is None: # Use a default value for forks that don't have a transaction gas limit cap return [ - pytest.param(TX_GAS_LIMIT + 1, None, id="tx_gas_limit_cap_none"), + pytest.param(Spec.tx_gas_limit_cap + 1, None, id="tx_gas_limit_cap_none"), ] return [ @@ -97,7 +100,7 @@ def test_transaction_gas_limit_cap( if tx_type == 3: # Type 3: EIP-4844 Blob Transaction tx_kwargs["max_fee_per_blob_gas"] = fork.min_base_fee_per_blob_gas() - tx_kwargs["blob_versioned_hashes"] = add_kzg_version([0], BLOB_COMMITMENT_VERSION_KZG) + tx_kwargs["blob_versioned_hashes"] = add_kzg_version([0], Spec.blob_commitment_version_kzg) elif tx_type == 4: # Type 4: EIP-7702 Set Code Transaction signer = pre.fund_eoa(amount=0) @@ -115,83 +118,327 @@ def test_transaction_gas_limit_cap( state_test(env=env, pre=pre, post=post, tx=tx) -@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True) -@pytest.mark.exception_test -def test_transaction_gas_limit_cap_at_transition( +@pytest.mark.parametrize( + "opcode", + [ + pytest.param(Op.CALL), + pytest.param(Op.DELEGATECALL), + pytest.param(Op.CALLCODE), + pytest.param(Op.STATICCALL), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_subcall_context( + blockchain_test: BlockchainTestFiller, pre: Alloc, opcode: Op, fork: Fork, env: Environment +): + """Test the transaction gas limit cap behavior for subcall context.""" + caller_address = pre.deploy_contract( + code=Op.SSTORE( + 0, + opcode( + gas=Op.CALLDATALOAD(0), + address=pre.deploy_contract(code=Op.MSTORE(0, Op.GAS) + Op.RETURN(0, 0x20)), + ret_offset=0, + ret_size=0, + ), + ) + ) + + # Passing tx limit cap as the gas parameter to *CALL operations + # All tests should pass and the *CALL operations should succeed + # Gas forwarded = min(remaining gas, specified gas parameter) + + txs = [ + Transaction( + to=caller_address, + sender=pre.fund_eoa(), + gas_limit=Spec.tx_gas_limit_cap, + data=bytes(Spec.tx_gas_limit_cap + modifier), + ) + for modifier in [-1, 0, 1] + ] + + post = { + caller_address: Account(storage={"0x00": 1}), + } + + blockchain_test(env=env, pre=pre, post=post, blocks=[Block(txs=txs)]) + + +@pytest.mark.parametrize( + "exceed_block_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_larger_than_block_gas_limit( blockchain_test: BlockchainTestFiller, pre: Alloc, + env: Environment, + exceed_block_gas_limit: bool, +): + """Test multiple transactions with total gas larger than the block gas limit.""" + code = Op.JUMPDEST + Op.JUMP(0) # Gas Cost = 1 (JUMPDEST) + 3 (PUSH1) + 8 (JUMP) = 12 + + tx_count = env.gas_limit // Spec.tx_gas_limit_cap + + block = Block( + txs=[ + Transaction( + to=pre.deploy_contract(code=code), + sender=pre.fund_eoa(), + gas_limit=Spec.tx_gas_limit_cap, + error=TransactionException.GAS_ALLOWANCE_EXCEEDED if i >= tx_count else None, + ) + for i in range(tx_count + int(exceed_block_gas_limit)) + ], + exception=BlockException.GASLIMIT_TOO_BIG if exceed_block_gas_limit else None, + ) + + blockchain_test(env=env, pre=pre, post={}, blocks=[block]) + + +@pytest.fixture +def total_cost_floor_per_token(): + """Total cost floor per token.""" + return 10 + + +@pytest.mark.parametrize( + "exceed_tx_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.parametrize("zero_byte", [True, False]) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_full_calldata( + state_test: StateTestFiller, + pre: Alloc, + zero_byte: bool, + total_cost_floor_per_token: int, + exceed_tx_gas_limit: bool, fork: Fork, ): - """ - Test transaction gas limit cap behavior at the Osaka transition. + """Test the transaction gas limit cap behavior for full calldata.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() - Before timestamp 15000: No gas limit cap (transactions with gas > 2^24 are valid) - At/after timestamp 15000: Gas limit cap of 2^24 is enforced - """ - sender = pre.fund_eoa() - contract_address = pre.deploy_contract( - code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + Op.STOP, + max_tokens_in_calldata = gas_available // total_cost_floor_per_token + num_of_bytes = max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4 + + num_of_bytes += int(exceed_tx_gas_limit) + + # Gas cost calculation based on EIP-7623: (https://eips.ethereum.org/EIPS/eip-7623) + # + # Simplified in this test case: + # - No execution gas used (no opcodes are executed) + # - Not a contract creation (no initcode) + # + # Token accounting: + # tokens_in_calldata = zero_bytes + 4 * non_zero_bytes + + byte_data = b"\x00" if zero_byte else b"\xff" + + tx = Transaction( + to=pre.fund_eoa(), + data=byte_data * num_of_bytes, + gas_limit=Spec.tx_gas_limit_cap, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + if exceed_tx_gas_limit + else None, ) - pre_cap = fork.transaction_gas_limit_cap() - if pre_cap is None: - pre_cap = TX_GAS_LIMIT - - # Transaction with gas limit above the cap before transition - high_gas_tx = Transaction( - ty=0, # Legacy transaction - to=contract_address, - gas_limit=pre_cap + 1, - data=b"", - value=0, - sender=sender, + state_test( + pre=pre, + post={}, + tx=tx, ) - post_cap = fork.transaction_gas_limit_cap(timestamp=15_000) - post_cap_tx_error = TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM - assert post_cap is not None, "Post cap should not be None" - assert post_cap <= pre_cap, ( - "Post cap should be less than or equal to pre cap, test needs update" +@pytest.mark.parametrize( + "exceed_tx_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_contract_creation( + state_test: StateTestFiller, + pre: Alloc, + total_cost_floor_per_token: int, + exceed_tx_gas_limit: bool, + fork: Fork, +): + """Test the transaction gas limit cap behavior for contract creation.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_available = Spec.tx_gas_limit_cap - intrinsic_cost(contract_creation=True) + + max_tokens_in_calldata = gas_available // total_cost_floor_per_token + num_of_bytes = (max_tokens_in_calldata // 4) + int(exceed_tx_gas_limit) + + # Cannot exceed max contract code size + num_of_bytes = min(num_of_bytes, fork.max_code_size()) + + code = Op.JUMPDEST * num_of_bytes + + # Craft a contract creation transaction that exceeds the transaction gas limit cap + # + # Total cost = + # intrinsic cost (base tx cost + contract creation cost) + # + calldata cost + # + init code execution cost + # + # The contract body is filled with JUMPDEST instructions, so: + # total cost = intrinsic cost + calldata cost + (num_of_jumpdest * 1 gas) + # + # If the total cost exceeds the tx limit cap, the transaction should fail + + total_cost = intrinsic_cost(contract_creation=True, calldata=code) + num_of_bytes + + tx = Transaction( + to=None, + data=code, + gas_limit=Spec.tx_gas_limit_cap, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + if total_cost > Spec.tx_gas_limit_cap + else None, ) - # Transaction with gas limit at the cap - cap_gas_tx = Transaction( - ty=0, # Legacy transaction - to=contract_address, - gas_limit=post_cap + 1, - data=b"", - value=0, - sender=sender, - error=post_cap_tx_error, + state_test( + pre=pre, + post={}, + tx=tx, ) - blocks = [] - # Before transition (timestamp < 15000): high gas transaction should succeed - blocks.append( - Block( - timestamp=14_999, - txs=[high_gas_tx], +@pytest.mark.parametrize( + "exceed_tx_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_access_list( + state_test: StateTestFiller, + exceed_tx_gas_limit: bool, + pre: Alloc, + fork: Fork, +): + """Test the transaction gas limit cap behavior for access list.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + + gas_costs = fork.gas_costs() + gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS + gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE + + gas_after_address = gas_available - gas_per_address + num_storage_keys = gas_after_address // gas_per_storage_key + int(exceed_tx_gas_limit) + + access_address = Address("0x1234567890123456789012345678901234567890") + storage_keys = [] + for i in range(num_storage_keys): + storage_keys.append(Hash(i)) + + access_list = [ + AccessList( + address=access_address, + storage_keys=storage_keys, ) + ] + + tx = Transaction( + to=pre.fund_eoa(), + gas_limit=Spec.tx_gas_limit_cap, + sender=pre.fund_eoa(), + access_list=access_list, + error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, ) - # At transition (timestamp = 15000): high gas transaction should fail - blocks.append( - Block( - timestamp=15_000, - txs=[cap_gas_tx], # Only transaction at the cap succeeds - exception=post_cap_tx_error, - ) + state_test( + pre=pre, + post={}, + tx=tx, ) - # Post state: storage should be updated by successful transactions - post = { - contract_address: Account( - storage={ - 0: 1, # Set by first transaction (before transition) - } + +@pytest.mark.parametrize( + "exceed_tx_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_authorized_tx( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + exceed_tx_gas_limit: bool, +): + """Test a transaction limit cap with authorized tx.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + + gas_costs = fork.gas_costs() + gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS + + per_empty_account_cost = 25_000 + auth_list_length = gas_available // (gas_per_address + per_empty_account_cost) + int( + exceed_tx_gas_limit + ) + + # EIP-7702 authorization transaction cost: + # + # 21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + # + 1900 * access list storage key count + # + 2400 * access list address count + # + PER_EMPTY_ACCOUNT_COST * authorization list length + # + # There is no calldata and no storage keys in this test case + # and the access address list count is equal to the authorization list length + # total cost = 21000 + (2400 + 25_000) * auth_list_length + + auth_address = pre.deploy_contract(code=Op.STOP) + + auth_signers = [pre.fund_eoa() for _ in range(auth_list_length)] + + access_list = [ + AccessList( + address=addr, + storage_keys=[], ) - } + for addr in auth_signers + ] + + auth_tuples = [ + AuthorizationTuple( + signer=signer, + address=auth_address, + nonce=signer.nonce, + ) + for signer in auth_signers + ] - blockchain_test(pre=pre, blocks=blocks, post=post) + tx = Transaction( + to=pre.fund_eoa(), + gas_limit=Spec.tx_gas_limit_cap, + sender=pre.fund_eoa(), + access_list=access_list, + authorization_list=auth_tuples, + error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, + ) + + state_test( + pre=pre, + post={}, + tx=tx, + ) From 09d1596d76802efd149de760e4f029093de1a4ef Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Thu, 14 Aug 2025 12:12:16 +0800 Subject: [PATCH 3/4] test(eip7825): add extra case for access list --- .../test_tx_gas_limit.py | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) 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 e8596e3df9f..eedca3239c9 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 @@ -326,13 +326,13 @@ def test_tx_gas_limit_cap_contract_creation( ], ) @pytest.mark.valid_from("Osaka") -def test_tx_gas_limit_cap_access_list( +def test_tx_gas_limit_cap_access_list_with_diff_keys( state_test: StateTestFiller, exceed_tx_gas_limit: bool, pre: Alloc, fork: Fork, ): - """Test the transaction gas limit cap behavior for access list.""" + """Test the transaction gas limit cap behavior for access list with different storage keys.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() @@ -370,6 +370,55 @@ def test_tx_gas_limit_cap_access_list( ) +@pytest.mark.parametrize( + "exceed_tx_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("Osaka") +def test_tx_gas_limit_cap_access_list_with_diff_addr( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + exceed_tx_gas_limit: bool, +): + """Test the transaction gas limit cap behavior for access list with different addresses.""" + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + + gas_costs = fork.gas_costs() + gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS + gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE + + account_num = gas_available // (gas_per_address + gas_per_storage_key) + int( + exceed_tx_gas_limit + ) + + access_list = [ + AccessList( + address=pre.fund_eoa(), + storage_keys=[Hash(i)], + ) + for i in range(account_num) + ] + + tx = Transaction( + to=pre.fund_eoa(), + gas_limit=Spec.tx_gas_limit_cap, + sender=pre.fund_eoa(), + access_list=access_list, + error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, + ) + + state_test( + pre=pre, + post={}, + tx=tx, + ) + + @pytest.mark.parametrize( "exceed_tx_gas_limit", [ From 7ec7c1a1fd76642b6f12690b9e616074c8b56a99 Mon Sep 17 00:00:00 2001 From: LouisTsai Date: Fri, 22 Aug 2025 19:00:36 +0800 Subject: [PATCH 4/4] refactor: update test logic --- .../test_modexp_upper_bounds.py | 2 +- .../test_tx_gas_limit.py | 196 +++++++++++++----- .../test_tx_gas_limit_transition_fork.py | 23 +- 3 files changed, 162 insertions(+), 59 deletions(-) diff --git a/tests/osaka/eip7823_modexp_upper_bounds/test_modexp_upper_bounds.py b/tests/osaka/eip7823_modexp_upper_bounds/test_modexp_upper_bounds.py index 3854aff7e00..269e4792964 100644 --- a/tests/osaka/eip7823_modexp_upper_bounds/test_modexp_upper_bounds.py +++ b/tests/osaka/eip7823_modexp_upper_bounds/test_modexp_upper_bounds.py @@ -10,13 +10,13 @@ from ethereum_test_tools.vm.opcode import Opcodes as Op from ...byzantium.eip198_modexp_precompile.helpers import ModExpInput, ModExpOutput -from ..eip7825_transaction_gas_limit_cap.test_tx_gas_limit import TX_GAS_LIMIT from ..eip7883_modexp_gas_increase.spec import Spec, Spec7883 REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7823.md" REFERENCE_SPEC_VERSION = "c8321494fdfbfda52ad46c3515a7ca5dc86b857c" MAX_LENGTH_BYTES = 1024 +TX_GAS_LIMIT = 2**24 @pytest.fixture 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 eedca3239c9..c40337763ab 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 @@ -7,7 +7,6 @@ import pytest -from ethereum_test_exceptions import BlockException from ethereum_test_forks import Fork from ethereum_test_tools import ( AccessList, @@ -129,14 +128,17 @@ def test_transaction_gas_limit_cap( ) @pytest.mark.valid_from("Osaka") def test_tx_gas_limit_cap_subcall_context( - blockchain_test: BlockchainTestFiller, pre: Alloc, opcode: Op, fork: Fork, env: Environment + state_test: StateTestFiller, pre: Alloc, opcode: Op, fork: Fork, env: Environment ): """Test the transaction gas limit cap behavior for subcall context.""" + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + caller_address = pre.deploy_contract( code=Op.SSTORE( 0, opcode( - gas=Op.CALLDATALOAD(0), + gas=tx_gas_limit_cap + 1, address=pre.deploy_contract(code=Op.MSTORE(0, Op.GAS) + Op.RETURN(0, 0x20)), ret_offset=0, ret_size=0, @@ -148,21 +150,17 @@ def test_tx_gas_limit_cap_subcall_context( # All tests should pass and the *CALL operations should succeed # Gas forwarded = min(remaining gas, specified gas parameter) - txs = [ - Transaction( - to=caller_address, - sender=pre.fund_eoa(), - gas_limit=Spec.tx_gas_limit_cap, - data=bytes(Spec.tx_gas_limit_cap + modifier), - ) - for modifier in [-1, 0, 1] - ] + tx = Transaction( + to=caller_address, + sender=pre.fund_eoa(), + gas_limit=tx_gas_limit_cap, + ) post = { caller_address: Account(storage={"0x00": 1}), } - blockchain_test(env=env, pre=pre, post=post, blocks=[Block(txs=txs)]) + state_test(env=env, pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -177,40 +175,45 @@ def test_tx_gas_larger_than_block_gas_limit( blockchain_test: BlockchainTestFiller, pre: Alloc, env: Environment, + fork: Fork, exceed_block_gas_limit: bool, ): """Test multiple transactions with total gas larger than the block gas limit.""" - code = Op.JUMPDEST + Op.JUMP(0) # Gas Cost = 1 (JUMPDEST) + 3 (PUSH1) + 8 (JUMP) = 12 + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" - tx_count = env.gas_limit // Spec.tx_gas_limit_cap + tx_count = env.gas_limit // tx_gas_limit_cap + gas_spender_contract = pre.deploy_contract(code=Op.INVALID) block = Block( txs=[ Transaction( - to=pre.deploy_contract(code=code), + to=gas_spender_contract, sender=pre.fund_eoa(), - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit_cap, error=TransactionException.GAS_ALLOWANCE_EXCEEDED if i >= tx_count else None, ) for i in range(tx_count + int(exceed_block_gas_limit)) ], - exception=BlockException.GASLIMIT_TOO_BIG if exceed_block_gas_limit else None, + exception=TransactionException.GAS_ALLOWANCE_EXCEEDED if exceed_block_gas_limit else None, ) blockchain_test(env=env, pre=pre, post={}, blocks=[block]) @pytest.fixture -def total_cost_floor_per_token(): +def total_cost_floor_per_token(fork: Fork): """Total cost floor per token.""" - return 10 + gas_costs = fork.gas_costs() + return gas_costs.G_TX_DATA_FLOOR_TOKEN_COST @pytest.mark.parametrize( - "exceed_tx_gas_limit", + "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ - pytest.param(True, marks=pytest.mark.exception_test), - pytest.param(False), + pytest.param(True, False, marks=pytest.mark.exception_test), + pytest.param(True, True, marks=pytest.mark.exception_test), + pytest.param(False, True), ], ) @pytest.mark.parametrize("zero_byte", [True, False]) @@ -221,11 +224,14 @@ def test_tx_gas_limit_cap_full_calldata( zero_byte: bool, total_cost_floor_per_token: int, exceed_tx_gas_limit: bool, + correct_intrinsic_cost_in_transaction_gas_limit: bool, fork: Fork, ): """Test the transaction gas limit cap behavior for full calldata.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + gas_available = tx_gas_limit_cap - intrinsic_cost() max_tokens_in_calldata = gas_available // total_cost_floor_per_token num_of_bytes = max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4 @@ -243,12 +249,30 @@ def test_tx_gas_limit_cap_full_calldata( byte_data = b"\x00" if zero_byte else b"\xff" + correct_intrinsic_cost = intrinsic_cost(calldata=byte_data * num_of_bytes) + if exceed_tx_gas_limit: + assert correct_intrinsic_cost > tx_gas_limit_cap, ( + "Correct 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 equal to the tx gas limit cap" + ) + + tx_gas_limit = ( + correct_intrinsic_cost + if correct_intrinsic_cost_in_transaction_gas_limit + else tx_gas_limit_cap + ) + tx = Transaction( to=pre.fund_eoa(), data=byte_data * num_of_bytes, - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit, sender=pre.fund_eoa(), - error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + error=TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + if correct_intrinsic_cost_in_transaction_gas_limit and exceed_tx_gas_limit + else TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST if exceed_tx_gas_limit else None, ) @@ -263,7 +287,7 @@ def test_tx_gas_limit_cap_full_calldata( @pytest.mark.parametrize( "exceed_tx_gas_limit", [ - pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(True), pytest.param(False), ], ) @@ -277,7 +301,9 @@ def test_tx_gas_limit_cap_contract_creation( ): """Test the transaction gas limit cap behavior for contract creation.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_available = Spec.tx_gas_limit_cap - intrinsic_cost(contract_creation=True) + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + gas_available = tx_gas_limit_cap - intrinsic_cost(contract_creation=True) max_tokens_in_calldata = gas_available // total_cost_floor_per_token num_of_bytes = (max_tokens_in_calldata // 4) + int(exceed_tx_gas_limit) @@ -304,10 +330,10 @@ def test_tx_gas_limit_cap_contract_creation( tx = Transaction( to=None, data=code, - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit_cap, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST - if total_cost > Spec.tx_gas_limit_cap + if total_cost > tx_gas_limit_cap else None, ) @@ -319,22 +345,26 @@ def test_tx_gas_limit_cap_contract_creation( @pytest.mark.parametrize( - "exceed_tx_gas_limit", + "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ - pytest.param(True, marks=pytest.mark.exception_test), - pytest.param(False), + pytest.param(True, False, marks=pytest.mark.exception_test), + pytest.param(True, True, marks=pytest.mark.exception_test), + pytest.param(False, True), ], ) @pytest.mark.valid_from("Osaka") def test_tx_gas_limit_cap_access_list_with_diff_keys( state_test: StateTestFiller, exceed_tx_gas_limit: bool, + correct_intrinsic_cost_in_transaction_gas_limit: bool, pre: Alloc, fork: Fork, ): """Test the transaction gas limit cap behavior for access list with different storage keys.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + gas_available = tx_gas_limit_cap - intrinsic_cost() gas_costs = fork.gas_costs() gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS @@ -355,12 +385,32 @@ def test_tx_gas_limit_cap_access_list_with_diff_keys( ) ] + correct_intrinsic_cost = intrinsic_cost(access_list=access_list) + if exceed_tx_gas_limit: + assert correct_intrinsic_cost > tx_gas_limit_cap, ( + "Correct 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 equal to the tx gas limit cap" + ) + + tx_gas_limit = ( + correct_intrinsic_cost + if correct_intrinsic_cost_in_transaction_gas_limit + else tx_gas_limit_cap + ) + tx = Transaction( to=pre.fund_eoa(), - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit, sender=pre.fund_eoa(), access_list=access_list, - error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, + error=TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + if correct_intrinsic_cost_in_transaction_gas_limit and exceed_tx_gas_limit + else TransactionException.INTRINSIC_GAS_TOO_LOW + if exceed_tx_gas_limit + else None, ) state_test( @@ -371,10 +421,11 @@ def test_tx_gas_limit_cap_access_list_with_diff_keys( @pytest.mark.parametrize( - "exceed_tx_gas_limit", + "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ - pytest.param(True, marks=pytest.mark.exception_test), - pytest.param(False), + pytest.param(True, False, marks=pytest.mark.exception_test), + pytest.param(True, True, marks=pytest.mark.exception_test), + pytest.param(False, True), ], ) @pytest.mark.valid_from("Osaka") @@ -383,10 +434,13 @@ def test_tx_gas_limit_cap_access_list_with_diff_addr( pre: Alloc, fork: Fork, exceed_tx_gas_limit: bool, + correct_intrinsic_cost_in_transaction_gas_limit: bool, ): """Test the transaction gas limit cap behavior for access list with different addresses.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + gas_available = tx_gas_limit_cap - intrinsic_cost() gas_costs = fork.gas_costs() gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS @@ -404,12 +458,32 @@ def test_tx_gas_limit_cap_access_list_with_diff_addr( for i in range(account_num) ] + correct_intrinsic_cost = intrinsic_cost(access_list=access_list) + if exceed_tx_gas_limit: + assert correct_intrinsic_cost > tx_gas_limit_cap, ( + "Correct 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 equal to the tx gas limit cap" + ) + + tx_gas_limit = ( + correct_intrinsic_cost + if correct_intrinsic_cost_in_transaction_gas_limit + else tx_gas_limit_cap + ) + tx = Transaction( to=pre.fund_eoa(), - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit, sender=pre.fund_eoa(), access_list=access_list, - error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, + error=TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + if correct_intrinsic_cost_in_transaction_gas_limit and exceed_tx_gas_limit + else TransactionException.INTRINSIC_GAS_TOO_LOW + if exceed_tx_gas_limit + else None, ) state_test( @@ -420,10 +494,11 @@ def test_tx_gas_limit_cap_access_list_with_diff_addr( @pytest.mark.parametrize( - "exceed_tx_gas_limit", + "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ - pytest.param(True, marks=pytest.mark.exception_test), - pytest.param(False), + pytest.param(True, False, marks=pytest.mark.exception_test), + pytest.param(True, True, marks=pytest.mark.exception_test), + pytest.param(False, True), ], ) @pytest.mark.valid_from("Osaka") @@ -432,10 +507,13 @@ def test_tx_gas_limit_cap_authorized_tx( pre: Alloc, fork: Fork, exceed_tx_gas_limit: bool, + correct_intrinsic_cost_in_transaction_gas_limit: bool, ): """Test a transaction limit cap with authorized tx.""" intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_available = Spec.tx_gas_limit_cap - intrinsic_cost() + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + assert tx_gas_limit_cap is not None, "Fork does not have a transaction gas limit cap" + gas_available = tx_gas_limit_cap - intrinsic_cost() gas_costs = fork.gas_costs() gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS @@ -477,13 +555,35 @@ def test_tx_gas_limit_cap_authorized_tx( for signer in auth_signers ] + correct_intrinsic_cost = intrinsic_cost( + access_list=access_list, authorization_list_or_count=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" + ) + else: + assert correct_intrinsic_cost <= tx_gas_limit_cap, ( + "Correct intrinsic cost should be less than or equal to the tx gas limit cap" + ) + + tx_gas_limit = ( + correct_intrinsic_cost + if correct_intrinsic_cost_in_transaction_gas_limit + else tx_gas_limit_cap + ) + tx = Transaction( to=pre.fund_eoa(), - gas_limit=Spec.tx_gas_limit_cap, + gas_limit=tx_gas_limit, sender=pre.fund_eoa(), access_list=access_list, authorization_list=auth_tuples, - error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceed_tx_gas_limit else None, + error=TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + if correct_intrinsic_cost_in_transaction_gas_limit and exceed_tx_gas_limit + else TransactionException.INTRINSIC_GAS_TOO_LOW + if exceed_tx_gas_limit + else None, ) state_test( 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 59c5bc69045..65190146952 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 @@ -16,7 +16,10 @@ ) from ethereum_test_tools.vm.opcode import Opcodes as Op -from .spec import Spec +from .spec import ref_spec_7825 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7825.git_path +REFERENCE_SPEC_VERSION = ref_spec_7825.version @pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True) @@ -38,27 +41,27 @@ def test_transaction_gas_limit_cap_at_transition( ) pre_cap = fork.transaction_gas_limit_cap() - if pre_cap is None: - pre_cap = Spec.tx_gas_limit_cap + post_cap = fork.transaction_gas_limit_cap(timestamp=15_000) + assert post_cap is not None, "Post cap should not be None" + + pre_cap = pre_cap if pre_cap else post_cap + 1 + + assert post_cap <= pre_cap, ( + "Post cap should be less than or equal to pre cap, test needs update" + ) # Transaction with gas limit above the cap before transition high_gas_tx = Transaction( ty=0, # Legacy transaction to=contract_address, - gas_limit=pre_cap + 1, + gas_limit=pre_cap, data=b"", value=0, sender=sender, ) - post_cap = fork.transaction_gas_limit_cap(timestamp=15_000) post_cap_tx_error = TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM - assert post_cap is not None, "Post cap should not be None" - assert post_cap <= pre_cap, ( - "Post cap should be less than or equal to pre cap, test needs update" - ) - # Transaction with gas limit at the cap cap_gas_tx = Transaction( ty=0, # Legacy transaction