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 89a503f412a..bc97783f08e 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( @@ -793,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. """ @@ -815,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, @@ -850,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_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index df255111a23..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()) 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 da283a5fee1..61cd928c2a5 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 @@ -327,27 +327,28 @@ def test_mixed_validity_multi_auth_receipt_gas( one invalid authorization. Every authorization tuple, valid or invalid, is charged the full - regular + state per-authorization intrinsic. Refunds, however, fire - only for the *valid* authorization whose authority leaf already - exists: it 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`` and contributes no refund on either - channel. + 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 refundable (valid, existing-leaf) - authorizations. 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``. + ``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 = 1`` uniformly and every + 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). @@ -418,7 +419,7 @@ def test_mixed_validity_multi_auth_receipt_gas( raise ValueError(f"unknown invalidity: {invalidity!r}") n = len(authorization_list) - refundable = 1 # exactly one valid, existing-leaf authorization + regular_refundable = 2 total_intrinsic = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, @@ -426,22 +427,25 @@ def test_mixed_validity_multi_auth_receipt_gas( intrinsic_state = fork.transaction_intrinsic_state_gas( authorization_count=n, ) - # Only the valid existing-leaf authorization refills the state - # channel (NEW_ACCOUNT); the invalid tuple refunds nothing. The - # refill is subtracted first and is not subject to the one-fifth cap. - state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT * refundable + # 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( - refundable * account_write, + regular_refundable * account_write, gas_before_regular_refund // fork.max_refund_quotient(), ) - # With only a single refundable authorization the one-fifth cap is - # generous, so the full ACCOUNT_WRITE clears on the regular channel. - assert regular_refund == refundable * account_write + # 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( 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/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)