Skip to content

refactor(tests): EIP-8037 test gas calculation logic and post state verification - #3383

Open
LouisTsai-Csie wants to merge 3 commits into
ethereum:forks/amsterdamfrom
LouisTsai-Csie:enhance-8037-coverage
Open

refactor(tests): EIP-8037 test gas calculation logic and post state verification#3383
LouisTsai-Csie wants to merge 3 commits into
ethereum:forks/amsterdamfrom
LouisTsai-Csie:enhance-8037-coverage

Conversation

@LouisTsai-Csie

@LouisTsai-Csie LouisTsai-Csie commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Description

While extending EIP-8037 coverage I reviewed the existing suite and noticed that a number of tests do not restrict the behavior the test names and docstring intended to do. This PR marks those tests and starts tightening them.

Case 1: set state_gas_reservior to 0 does not mean no state gas charge

Take test_state_gas_selfdestruct.py::test_selfdestruct_existing_beneficiary_no_state_gas as a representative example:

In my opinion, some of the verification is not strict enough, below is an example for unmodified test_state_gas_selfdestruct:

@pytest.mark.valid_from("EIP8037")
def test_selfdestruct_existing_beneficiary_no_state_gas(
    state_test: StateTestFiller,
    pre: Alloc,
) -> None:
    """Test SELFDESTRUCT to existing beneficiary charges no state gas."""
    beneficiary = pre.fund_eoa(amount=0)

    contract = pre.deploy_contract(
        code=Op.SELFDESTRUCT(beneficiary),
        balance=1,
    )

    tx = Transaction(
        to=contract,
        state_gas_reservoir=0,
        sender=pre.fund_eoa(),
    )

    state_test(pre=pre, post={}, tx=tx)

The test intends to show that a SELFDESTRUCT sweep to an existing beneficiary charges no state gas. It has two independent problems.

  1. It exercises the opposite scenario. pre.fund_eoa(amount=0) returns a fresh address but writes nothing to the pre-allocation, so the selfdestruting actually create a new account, charging the NEW_ACCOUNT cost. However, the test implementation does not catch the error at all.

  2. In this test, the tx.gas_limit is not configured, while the state_gas_reservior configure to 0. In this case, the transaction gas limit case would be configured to transaction gas limit cap. So it is possible that a misimplemented client draw gas from gas_left and increase state_gas_from_gas_left , and the transaction is still possible to pass.

Transactions are constructed via BlockchainTest::generate_block_data, which calls with_gas_limit() to set gas limits:

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
]

If gas_limit is not already set, with_gas_limit() calculates it implicitly:

if "gas_limit" not in self.model_fields_set:
    updated_values["gas_limit"] = self._calculate_implicit_gas_limit(...)

In _calculate_implicit_gas_limit(), when state_gas_reservoir_enabled is True, the transaction gas limit becomes:

tx_gas_limit = min(tx_gas_limit, transaction_gas_limit_cap) + state_gas_reservoir

To actually constrain the state dimension, a test needs one of: (1) Header verification, and (2) A transaction gas limit that excludes the state cost.

Case 2: If the test is intended to validate state gas calculation, the header gas cost should not be derived from max(execution_gas, state_gas)

Take test_create_selfdestruct_code_deposit_no_refund_header_check as an example. This test verifies that code deposit costs are not refunded (state gas cost dimension). Currently, using max(state_gas, execution_gas) works fine because state gas always dominates in Amsterdam.

However, consider future upgrades where: (1) execution gas costs for certain operations increase significantly, or (2) state gas costs drop substantially. If execution_gas ever exceeds state_gas, the test would still pass -> but it would no longer validate the state gas for "no refund" scenario. The test would be measuring a different code path without catching the regression.

In other words: the test's intention (validating no-refund behavior) now depends on which gas component is larger. Using max() masks this dependency and creates a brittle test that silently fails to catch the intended behavior change.

There are several instances in the test suite, this PR adds stricter verification to tighten it.

Related Issues or PRs

N/A.

Checklist

  • Ran fast static checks to avoid CI fails, see Code Standards & Verifying Changes: just static
  • PR title has the form <type>(<area>): <title>, where <type> and <area> come from an appropriate C-<type>, respectively A-<area>, label. The title should match the target squash commit message.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@LouisTsai-Csie LouisTsai-Csie added C-refactor Category: refactor A-tests Area: Consensus tests. labels Aug 17, 2026
@LouisTsai-Csie LouisTsai-Csie self-assigned this Aug 17, 2026
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.53%. Comparing base (5b2b22c) to head (9e22aab).
⚠️ Report is 18 commits behind head on forks/amsterdam.

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #3383   +/-   ##
================================================
  Coverage            93.53%   93.53%           
================================================
  Files                  624      624           
  Lines                37070    37074    +4     
  Branches              3394     3394           
================================================
+ Hits                 34675    34679    +4     
  Misses                1645     1645           
  Partials               750      750           
Flag Coverage Δ
unittests 93.53% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@spencer-tb
spencer-tb self-requested a review August 17, 2026 09:34
Block(
txs=txs,
header_verify=Header(
gas_used=max(block_execution, block_state)

@spencer-tb spencer-tb Aug 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make the intended state dominance explicit here and pin the header directly to block_state?

assert block_state > block_execution, "requires state gas to dominate"
...
header_verify=Header(gas_used=block_state)

stop_execution = intrinsic_calc()

expected = max(create_execution + stop_execution, create_state_gas)
expected = max(create_execution + stop_execution, create_state)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we assert that state dominates and pin expected = create_state? To ensure this test specifically validates the CREATE state gas charges

execution gas plus the account-creation state gas, and not the
legacy combined execution account-creation cost.
"""
# TODO: Modify to subcall scenario

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add this to the testnet tracker issue?

@pytest.mark.parametrize(
"num_txs,num_sstores",
[
pytest.param(1, 1, id="single_sstore_single_tx"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be dropped?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tests Area: Consensus tests. C-refactor Category: refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants