Skip to content

feat(tests): complete EIP-7981 coverage and testing checklist - #3537

Open
danceratopz wants to merge 13 commits into
ethereum:forks/amsterdamfrom
danceratopz:7981-improve-tests
Open

danceratopz wants to merge 13 commits into
ethereum:forks/amsterdamfrom
danceratopz:7981-improve-tests

Conversation

@danceratopz

@danceratopz danceratopz commented Sep 7, 2026

Copy link
Copy Markdown
Member

Description

Strengthen EIP-7981 tests to check successful execution and exact gas charges.

  • Every transaction built by the shared fixtures now carries an expected receipt with success status and the exact gas used, and a gas limit that also funds the top-frame authorization gas of type 4 transactions. A default surplus of 1000 gas shows that unused gas is returned; the exact gas limit test sets the surplus to zero.
  • Add 4 tests covering the billing crossover, recipient and value interactions, the execution out-of-gas boundary, and refund and revert interactions.
  • Cover both outcomes at the calldata floor boundary with exact upfront funding, cover duplicate storage keys within one entry, and record the floor exception when the floor check is what rejects a transaction.
  • Realign checklist markers on 5 tests and document the coverage and N/A decisions in the checklist files.
  • Correct the EIP reference pin to the blob hash of the revision that was already pinned by commit hash; the pinned content is unchanged.

Nimbus rejects a block that has transactions and a header gasUsed of zero before it validates any transaction, so it reports the intrinsic gas exception for the below-floor cases of the floor validation and exact-balance tests. Those cases pass on Nimbus once invalid-transaction blocks carry a nonzero header gasUsed, which is the subject of the follow-up #3599. This is kinda low prio. Currently exception mapping is disabled for nimbus (in the hive sim Dockerfiles), see #3603.

Validation at the current head: just static passes, and all 731 EIP-7981 fill cases pass with --until Amsterdam --generate-all-formats, including 184 EngineX fixtures and the activation transitions. The regenerated checklist covers all 15 applicable items with 254 items marked N/A and no unmarked tests. consume enginex against go-ethereum, Nethermind, ethrex, Erigon, Besu and Reth passes all 186 cases each; Nimbus passes 174 and fails the 12 below-floor cases named above until the follow-up lands. Devnet execution and consume engine remain open.

Related Issues or PRs

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

image

@danceratopz danceratopz added C-feat Category: an improvement or new feature A-test-cli Area: execution_testing.cli A-tests Area: Consensus tests. labels Sep 7, 2026
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.44%. Comparing base (52161d9) to head (e0e8200).
⚠️ Report is 4 commits behind head on forks/amsterdam.

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #3537   +/-   ##
================================================
  Coverage            94.44%   94.44%           
================================================
  Files                  624      624           
  Lines                36928    36928           
  Branches              3326     3326           
================================================
  Hits                 34875    34875           
  Misses                1450     1450           
  Partials               603      603           
Flag Coverage Δ
unittests 94.44% <ø> (ø)

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.

@danceratopz danceratopz removed the A-test-cli Area: execution_testing.cli label Sep 15, 2026
@danceratopz
danceratopz force-pushed the 7981-improve-tests branch 2 times, most recently from b9126b3 to 4b26471 Compare September 15, 2026 11:57
@danceratopz
danceratopz marked this pull request as ready for review September 15, 2026 12:31
@danceratopz
danceratopz marked this pull request as draft September 15, 2026 12:55
Expand test_access_list_data_cost_with_execution with calldata-floor
and execution-cost dominance for zero and nonzero calldata, retaining
transaction types 1 and 2. These cases catch a surcharge omitted from
either side of the gas-used maximum.

Derive the surcharge independently of the access-list calculator and
provide surplus gas so an out-of-gas transaction cannot satisfy the
receipt assertion simply by consuming the entire gas limit.

Validation: all 24 focused EELS fill cases and just static pass against
the unrefactored specification on forks/amsterdam.
Add test_execution_gas_after_access_list_surcharge.

The existing OOG checklist markers only cover transactions rejected
before execution for insufficient intrinsic or floor gas. They do not
prove that EIP-7981's surcharge is deducted before the EVM runs.

Add an exact execution-gas boundary around a warm SSTORE for transaction
types 1 and 2. Assert receipt status, gas used and storage rollback with
one gas too little, exact gas and one extra gas. This supplies actual
execution-OOG evidence for the gas-cost checklist item.

Validation: focused EELS fill and just static pass before this commit.
Build the transaction gas limit from the gas the transaction is
expected to use, including the top-frame authorization gas of type 4
transactions, and attach an expected receipt with success status and
that exact gas to every valid fixture-built transaction. Until now the
fixture limit stopped at the intrinsic cost, so type 4 transactions
ran out of gas while processing their authorization, and the
gas-measurement tests only checked acceptance.

Add a gas surplus fixture, on by default, so a receipt that equals the
gas limit cannot pass by consuming everything. The exact gas limit
test sets it to zero. Annotate the authorization fixtures with whether
they create an account so the top-frame gas is modeled.

Remove the manual receipt in the calldata floor test, which the
fixture now supersedes.
The shared error fixture returned the intrinsic gas exception for
every negative gas delta. When the calldata floor exceeds the
intrinsic cost, the gas limit sits between the two and the
specification rejects on the floor check, so record that exception.
This changes the floor validation test; the intrinsic gas cases keep
their exception.

Nimbus reports the intrinsic exception for these blocks because it
rejects a block with transactions and zero gas used before validating
any transaction. That needs invalid-transaction blocks to carry a
nonzero header gas used, which is a separate change.
Add test_access_list_floor_with_exact_balance.

The existing exact-balance scenario only rejected a gas limit midway
through the surcharge gap. The data-floor checklist requires both valid
and invalid transactions when the floor exceeds intrinsic gas.

Use floor minus one, exact floor and floor plus one limits. Assert
receipt status, gas used and the sender's final balance for valid cases,
with full upfront funding for both transaction types 1 and 2. Correct
the marker from execution OOG to data-floor intrinsic validity.

Validation: focused EELS fill and just static pass before this commit.
Map gas-limit and data-floor rejection to intrinsic validity rather
than execution OOG. Credit exact valid limits and contract creation
with their specific checklist items; preserve actual gas measurements.

Validation: module EELS fill and just static pass before this commit.
Both gas-transition tests already assert fork-specific billed gas
through receipts or sender balances. Add the gas-measurement mapping
alongside their existing before/after activation decorators. The
validity-transition test already covers all four required outcomes.

Validation: module EELS fill and just static pass before this commit.
Strengthen test_duplicate_access_list_entries.

The existing duplicate-address-entry case does not detect a client
that deduplicates storage keys within each entry. Add that distinct
shape to the measured duplicate test without multiplying unrelated
parameters. This is an optional improvement beyond checklist closure.

Validation: focused EELS fill and just static pass before this commit.
Add test_access_list_data_cost_at_crossover.

The existing surcharge regression tests well-separated floor and
execution dominance. Add the exact crossover and adjacent execution
gas values for zero and nonzero calldata, retaining the existing cases.
Use surplus gas and exact receipts to detect a misplaced max operation.
This is an independent improvement beyond checklist closure.

Validation: focused EELS fill and just static pass before this commit.
Add test_access_list_surcharge_with_refund.

EIP-7981 preserves refund rules, so the new-refund checklist section is
not applicable. Add independent interaction coverage: clear a warm
slot, optionally revert, and assert storage, receipt status and gas
after refunds with and without calldata-floor dominance. This checks
that the surcharged floor still binds and revert discards the refund.

Validation: focused EELS fill and just static pass before this commit.
Add test_access_list_surcharge_with_recipient_costs.

The surcharge must survive Amsterdam's different intrinsic bases for
self-transfers and value-bearing transfers. The previous cases only
used non-self recipients with zero value.

Add focused combinations of self/other recipients and zero/nonzero
value with and without floor dominance. Derive the expected surcharge
separately from calculators without an access list, and assert success
and billed gas with surplus gas. Use an existing funded recipient so
account creation cannot obscure the repricing being measured.

This is an optional interaction regression beyond checklist closure.
Validation: focused EELS fill and just static pass before this commit.
Replace the blanket transaction-type exclusion with exclusions for the
rules EIP-7981 leaves unchanged, keeping the exact and insufficient gas
limit, floor validity and contract creation items for the repriced
transaction types. Record the coverage evidence as self-contained
statements and mark the optional second-client instrumentation as not
collected rather than as validation.
Since ethereum#3223 the pin has been the hash of the EIPs commit that moved the
EIP to Review, so the version check never matched a blob. Use the blob
hash of eip-7981.md in that same commit. The pinned content is
unchanged. Relative to the blob pinned before ethereum#3223 (954963f), the only
difference is the status change from Draft to Review.
@danceratopz
danceratopz marked this pull request as ready for review September 16, 2026 11:14

@LouisTsai-Csie LouisTsai-Csie left a comment

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.

Thanks for the checklist PR. A few ideas:

  • Two-dimensional gas: execution cost with access list dominates header cost.
  • Combine test_access_list_data_cost_with_execution and test_access_list_data_cost_at_crossover as they have similar structure.

[pytest.param("eoa", id="")],
indirect=True,
)
def test_access_list_floor_cost_with_calldata(

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.

Test name and docstring indicate calldata floor domination, but pinning the delegated recipe in conftest means we can't guarantee that anymore. Like in this test, Intrinsic cost outperforms the floor.

Not a coverage gap though; at_crossover and with_refund already cover the floor.

Comment on lines +253 to +256
top_frame_gas = fork.transaction_top_frame_gas_calculator()(
contract_creation=contract_creating_tx,
authorizations=authorization_list or [],
)

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 transaction_top_frame_execution_gas instead of transaction_top_frame_gas_calculator? As the comparison is max(intrinsic + execution, floor). It is not breaking any tests since which no state gas is charged within the test suite.

Comment on lines +25 to +33
GAS_SURPLUS = 1000
"""
Gas added to the limit of valid fixture-built transactions.

With the exact expected gas as the limit, a receipt equal to the limit
cannot tell exact billing from consuming everything that was available.
The surplus makes the receipt show that unused gas is returned as well.
Boundary tests override it with zero through the `tx_gas_surplus` fixture.
"""

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.

This is only used by tx_gas_surplus, I think we could remove this.

Comment on lines 72 to 78
@pytest.fixture
def authorization_refund() -> bool:
"""
Return whether the transaction has an existing authority in the
authorization list.
"""
return False

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.

This is parameterized but always False. Is there missing coverage here?

Second, EIP2780.refund_types() filters out AUTHORIZATION_EXISTING_AUTHORITY, so in Amsterdam there is no such refunds. The flag now only decides whether the authority account pre-exists (hence its new creates_account wiring). So should we rename authorization_refund here?

Comment on lines 95 to 114
if not hasattr(request, "param"):
if tx_type == 4:
return [
AuthorizationTuple(
signer=pre.fund_eoa(1 if authorization_refund else 0),
address=Address(1),
creates_account=not authorization_refund,
)
]
return None
if request.param is None:
if tx_type == 4:
return [
AuthorizationTuple(
signer=pre.fund_eoa(1 if authorization_refund else 0),
address=Address(1),
creates_account=not authorization_refund,
)
]
return None

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.

We could fold these two statements into:

param = getattr(request, "param", None)
if param is None:
  if tx_type == 4:
    pass

transaction_type/test/intrinsic_validity/nonce_minus_one = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/nonce_plus_one = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/nonce_exact = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/to = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.

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.

I think this is covered by test_contract_creation_with_access_list?

Comment on lines +20 to +23
transaction_type/test/intrinsic_validity/value_non_zero_insufficient_balance = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/value_non_zero_sufficient_balance = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/value_zero_insufficient_balance = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.
transaction_type/test/intrinsic_validity/value_zero_sufficient_balance = EIP-7981 reprices existing access-list transactions; it does not introduce a transaction type or change this rule.

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.

I think we should still test out these cases!

  • value_non_zero_insufficient_balance
  • value_non_zero_sufficient_balance
  • value_zero_insufficient_balance
  • value_zero_sufficient_balance

@spencer-tb

Copy link
Copy Markdown
Contributor

Follow up review PR: danceratopz#61

Fire away! :)

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-feat Category: an improvement or new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants