Skip to content

feat(tests): adds EIP-7981 test and required framework changes#2144

Open
felix314159 wants to merge 3 commits intoethereum:eips/amsterdam/eip-7981from
felix314159:eip7981
Open

feat(tests): adds EIP-7981 test and required framework changes#2144
felix314159 wants to merge 3 commits intoethereum:eips/amsterdam/eip-7981from
felix314159:eip7981

Conversation

@felix314159
Copy link
Contributor

@felix314159 felix314159 commented Feb 5, 2026

🗒️ Description

uv run fill -v -s --clean ./tests/amsterdam/eip7981_increase_access_list_cost/ --until=amsterdam
TODO: test against clients

🔗 Related Issues or PRs

Solves Implementation Tracker
EIP 7981
Toni PR

✅ Checklist

  • All: Ran fast tox checks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:
    uvx tox -e static
  • All: PR title adheres to the repo standard - it will be used as the squash commit message and should start type(scope):.
  • All: Considered updating the online docs in the ./docs/ directory.
  • All: Set appropriate labels for the changes (only maintainers can apply labels).
  • Tests: Ran mkdocs serve locally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.
  • Tests: For PRs implementing a missed test case, update the post-mortem document to add an entry the list.
  • Ported Tests: All converted JSON/YML tests from ethereum/tests or tests/static have been assigned @ported_from marker.

Cute Animal Picture

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

@felix314159
Copy link
Contributor Author

@gurukamath feel free to make adjustments / add more tests

@gurukamath
Copy link
Contributor

@gurukamath feel free to make adjustments / add more tests

Sure. Will take a look

Copy link
Contributor

@spencer-tb spencer-tb left a comment

Choose a reason for hiding this comment

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

Nice work. I know this is in draft but just to keep the ball rolling.

I think it would be good to check we don't duplicate tests here that are already present in berlin/eip2930_access_list or prague/eip7623_increase_calldata_cost. If we update the EIPs here to include the access list in the fork aware floor cost calculation we should be able to remove some of the tests from this PR. For example:

  • tests/berlin/eip2930_access_list/test_tx_intrinsic_gas.py we can add access_list to: data_floor_gas_cost_calculator(data=data, ...), here.
  • tests/prague/eip7623_increase_calldata_cost/conftest.py there are two instances we can add the access_list to: here & here.

As these methods are fork aware as long we fill these tests for Amsterdam, I think we should be able to remove test_transactions_without_access_list with some certainty.

Fork transition tests too! ;)

)

# Calculate calldata floor cost
return fork_data_floor_cost_calculator(data=tx_data)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need the access list here?

Suggested change
return fork_data_floor_cost_calculator(data=tx_data)
return fork_data_floor_cost_calculator(data=tx_data, access_list=access_list)

return zero_bytes + nonzero_bytes * 4


def find_floor_cost_threshold(
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont think this helper is used anywhere?


pytestmark = pytest.mark.valid_at("Amsterdam")


Copy link
Contributor

Choose a reason for hiding this comment

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

Should we param with @pytest.mark.with_all_tx_types across most of these tests. It would be good to have blob and type 4 txs tested.

According to EIP-7981:
- total_data_tokens = tokens_in_calldata + tokens_in_access_list
- floor_gas = TX_BASE_COST + total_data_tokens * TOTAL_COST_FLOOR_PER_TOKEN
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

We could add these to tests, to help aid debugging between framework/specs calculations on a gas mismatch. Not essential imo.

Suggested change
"""
"""
tx.expected_receipt = TransactionReceipt(
cumulative_gas_used=tx_intrinsic_gas_cost_including_floor_data_cost
)

@@ -603,10 +608,6 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
zero_bytes += 1

tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4)
Copy link
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 refactor the token data counting to a separate helper function and just call that here and in the access list gas section. I am thinking something like

def count_tokens_in_data(data: bytes) -> Uint:
    """
    Count the tokens in an arbitrary input data.
    """
    zero_bytes = 0
    for byte in data:
        if byte == 0:
            zero_bytes += 1

    return Uint(zero_bytes + (len(data) - zero_bytes) * 4)

+ auth_cost
),
calldata_floor_gas_cost,
floor_gas_cost,
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also change the variable name in the validate_transaction function. The variable there continues to be called calldata_floor_gas_cost

@spencer-tb
Copy link
Contributor

Can we point the PR to the eips branch: gh pr edit <PR_NUMBER> --base eips/amsterdam/eip-7981

@felix314159
Copy link
Contributor Author

felix314159 commented Feb 6, 2026

Thanks for all the feedback @spencer-tb and @gurukamath. I implemented it, and after lunch I will try to rebase this PR on forks/amsterdam so that i can change the target fork to the EIP branch (which is based on forks/amsterdam). Right now this PR is based on the devnet2 branch (my fault), so if I now would just change the target branch it would pick up changes from our devnet branch that would make it hard to see what this PR does / hard to review / hard to run against clients due to too many changes files. From now on I will base new EIPs off of forks/amsterdam and when i want to run tests i locally run our workflow that creates the devnet branch.

Edit: Done! We now target the EIP branch

@felix314159 felix314159 changed the base branch from devnets/bal/2 to eips/amsterdam/eip-7981 February 6, 2026 15:25
@felix314159 felix314159 marked this pull request as ready for review February 6, 2026 15:28
@felix314159 felix314159 mentioned this pull request Feb 6, 2026
7 tasks
Comment on lines 572 to 583
def count_tokens_in_data(data: bytes) -> Uint:
"""
Count the tokens in an arbitrary input data.
"""
zero_bytes = 0
for byte in data:
if byte == 0:
zero_bytes += 1

return Uint(zero_bytes + (len(data) - zero_bytes) * 4)


Copy link
Contributor

Choose a reason for hiding this comment

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

This should be backported to previous forks.

if byte == 0:
zero_bytes += 1

return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be something like:

Suggested change
return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
return Uint(zero_bytes + (len(data) - zero_bytes) * CALLDATA_TOKENS_PER_NONZERO_BYTE)

?

zero_bytes = 0
for byte in data:
if byte == 0:
zero_bytes += 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the 1 be in a named constant? Something like CALLDATA_TOKENS_PER_ZERO_BYTE?

Comment on lines 576 to 581
zero_bytes = 0
for byte in data:
if byte == 0:
zero_bytes += 1

return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're iterating through all of data anyway, how about:

Suggested change
zero_bytes = 0
for byte in data:
if byte == 0:
zero_bytes += 1
return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
return sum(Uint(4) if byte else Uint(1) for byte in data)

Or if that's too Pythonic:

Suggested change
zero_bytes = 0
for byte in data:
if byte == 0:
zero_bytes += 1
return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
return sum(Uint(1) if byte == 0 else Uint(4) for byte in data)

Comment on lines 607 to 608
in the access list (addresses and storage keys), with zero bytes costing
1 token and non-zero bytes costing 4 tokens.
Copy link
Contributor

Choose a reason for hiding this comment

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

We should avoid putting constant values in docstrings. It's too easy to update the code and forget to update the docs. Instead, link to the relevant constant.

),
):
for access in tx.access_list:
# Storage access costs (EIP-2930)
Copy link
Contributor

Choose a reason for hiding this comment

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

You'll need to backport the comment as well.

else:
create_cost = Uint(0)

# EIP-7981: Calculate access list tokens and costs
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we should establish the pattern of commenting with an EIP number. If we take this approach, we'll end up with piles of comments where a block of code is introduced under EIP-X, then in a later fork EIP-Y will modify that block, making it misleading which EIP a sequence of lines belongs to. For example:

Fork T Fork T+1
# EIP-1234: The dingus is the rate of fleep      
dingus = a + b
dingus += c ^ d
dingus /= fleep(e)
# EIP-1234: The dingus is the rate of fleep      
dingus = a + b

# EIP-4567: Frobulate the dingus
dingus = frobulate(dingus)

dingus += c ^ d        # <-
dingus /= fleep(e)     # <-

The marked lines (<-) are now incorrectly attributed to EIP-4567 in Fork+1. Instead, I'd recommend omitting the EIP identifier in the comments, and instead describe the changes introduced by the EIP in the function's docstrings. The rendered diffs will make it pretty obvious what's changed.

Perhaps I'm being overly cautious, however.

@felix314159
Copy link
Contributor Author

Thanks for the feedback, I 'backported' all the way to Berlin (2930 introduced access lists). This code duplication is a feature right, so that each fork is standalone?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants