feat(tests): adds EIP-7981 test and required framework changes#2144
feat(tests): adds EIP-7981 test and required framework changes#2144felix314159 wants to merge 3 commits intoethereum:eips/amsterdam/eip-7981from
Conversation
|
@gurukamath feel free to make adjustments / add more tests |
Sure. Will take a look |
There was a problem hiding this comment.
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.pywe can addaccess_listto:data_floor_gas_cost_calculator(data=data, ...), here.tests/prague/eip7623_increase_calldata_cost/conftest.pythere are two instances we can add theaccess_listto: 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) |
There was a problem hiding this comment.
Does this need the access list here?
| 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( |
There was a problem hiding this comment.
I dont think this helper is used anywhere?
|
|
||
| pytestmark = pytest.mark.valid_at("Amsterdam") | ||
|
|
||
|
|
There was a problem hiding this comment.
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 | ||
| """ |
There was a problem hiding this comment.
We could add these to tests, to help aid debugging between framework/specs calculations on a gas mismatch. Not essential imo.
| """ | |
| """ | |
| 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) | |||
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
We should also change the variable name in the validate_transaction function. The variable there continues to be called calldata_floor_gas_cost
|
Can we point the PR to the eips branch: |
|
Thanks for all the feedback @spencer-tb and @gurukamath. I implemented it, and after lunch I will try to rebase this PR on Edit: Done! We now target the EIP branch |
| 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) | ||
|
|
||
|
|
There was a problem hiding this comment.
This should be backported to previous forks.
| if byte == 0: | ||
| zero_bytes += 1 | ||
|
|
||
| return Uint(zero_bytes + (len(data) - zero_bytes) * 4) |
There was a problem hiding this comment.
Should this be something like:
| 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 |
There was a problem hiding this comment.
Should the 1 be in a named constant? Something like CALLDATA_TOKENS_PER_ZERO_BYTE?
| zero_bytes = 0 | ||
| for byte in data: | ||
| if byte == 0: | ||
| zero_bytes += 1 | ||
|
|
||
| return Uint(zero_bytes + (len(data) - zero_bytes) * 4) |
There was a problem hiding this comment.
If we're iterating through all of data anyway, how about:
| 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:
| 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) |
| in the access list (addresses and storage keys), with zero bytes costing | ||
| 1 token and non-zero bytes costing 4 tokens. |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
You'll need to backport the comment as well.
| else: | ||
| create_cost = Uint(0) | ||
|
|
||
| # EIP-7981: Calculate access list tokens and costs |
There was a problem hiding this comment.
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.
|
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? |
…, do we really need this check
🗒️ Description
uv run fill -v -s --clean ./tests/amsterdam/eip7981_increase_access_list_cost/ --until=amsterdamTODO: test against clients
🔗 Related Issues or PRs
Solves Implementation Tracker
EIP 7981
Toni PR
✅ Checklist
toxchecks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:uvx tox -e statictype(scope):.mkdocs servelocally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.@ported_frommarker.Cute Animal Picture