Skip to content

Commit bc691d1

Browse files
danceratopzmarioevzspencer-tb
authored
feat(tests): add eip-2929 precompile address warming tests (#1495)
* feat(tests): verify precompile eip-2929 address warming * fix(consume): update old misnamed transition fork ruleset This seemed to be previously unused. * fix: review comments (#1504) * Update tests/frontier/precompiles/test_precompiles.py Co-authored-by: Mario Vega <[email protected]> * chore(docs): add changelog. * chore: move to berlin, fix typecheck. * chore(tests): increase precompile extra range. --------- Co-authored-by: Mario Vega <[email protected]> Co-authored-by: spencer <[email protected]> Co-authored-by: spencer-tb <[email protected]>
1 parent 9cc32c3 commit bc691d1

File tree

4 files changed

+153
-1
lines changed

4 files changed

+153
-1
lines changed

docs/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Users can expect that all tests currently living in [ethereum/tests](https://git
3636
-[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702): Test that DELEGATECALL to a 7702 target works as intended ([#1485](https://github.com/ethereum/execution-spec-tests/pull/1485)).
3737
-[EIP-2573](https://eips.ethereum.org/EIPS/eip-2537): Includes a BLS12 point generator, alongside additional coverage many of the precompiles ([#1350](https://github.com/ethereum/execution-spec-tests/pull/1350)), ([#1505](https://github.com/ethereum/execution-spec-tests/pull/1505)).
3838
- ✨ Add all [`GeneralStateTests` from `ethereum/tests`](https://github.com/ethereum/tests/tree/7dc757ec132e372b6178a016b91f4c639f366c02/src/GeneralStateTestsFiller) to `execution-spec-tests` located now at [tests/static/state_tests](https://github.com/ethereum/execution-spec-tests/tree/main/tests/static/state_tests) ([#1442](https://github.com/ethereum/execution-spec-tests/pull/1442)).
39+
-[EIP-2929](https://eips.ethereum.org/EIPS/eip-2929): Test that precompile addresses are cold/warm depending on the fork they are activated ([#1495](https://github.com/ethereum/execution-spec-tests/pull/1495)).
3940

4041
## [v4.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.3.0) - 2025-04-18
4142

src/pytest_plugins/consume/hive_simulators/ruleset.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def get_blob_schedule_entries(fork: Fork) -> Dict[str, int]:
302302
"HIVE_TERMINAL_TOTAL_DIFFICULTY": 0,
303303
"HIVE_SHANGHAI_TIMESTAMP": 0,
304304
},
305-
"MergeToShanghaiAtTime15k": {
305+
"ParisToShanghaiAtTime15k": {
306306
"HIVE_FORK_HOMESTEAD": 0,
307307
"HIVE_FORK_TANGERINE": 0,
308308
"HIVE_FORK_SPURIOUS": 0,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
abstract: Tests [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929)
3+
Test cases for [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929).
4+
""" # noqa: E501
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""
2+
abstract: Tests [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929)
3+
Test cases for [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929).
4+
""" # noqa: E501
5+
6+
from typing import Iterator, Tuple
7+
8+
import pytest
9+
10+
from ethereum_test_forks import (
11+
Fork,
12+
get_transition_fork_predecessor,
13+
get_transition_fork_successor,
14+
)
15+
from ethereum_test_tools import (
16+
Account,
17+
Address,
18+
Alloc,
19+
Block,
20+
BlockchainTestFiller,
21+
Transaction,
22+
)
23+
from ethereum_test_tools.vm.opcode import Opcodes as Op
24+
25+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-2929.md"
26+
REFERENCE_SPEC_VERSION = "0e11417265a623adb680c527b15d0cb6701b870b"
27+
28+
29+
def precompile_addresses_in_predecessor_successor(
30+
fork: Fork,
31+
) -> Iterator[Tuple[Address, bool, bool]]:
32+
"""
33+
Yield the addresses of precompiled contracts and whether they existed in the parent fork.
34+
35+
Args:
36+
fork (Fork): The transition fork instance containing precompiled contract information.
37+
38+
Yields:
39+
Iterator[Tuple[str, bool]]: A tuple containing the address in hexadecimal format and a
40+
boolean indicating whether the address has existed in the predecessor.
41+
42+
"""
43+
predecessor_precompiles = set(get_transition_fork_predecessor(fork).precompiles())
44+
successor_precompiles = set(get_transition_fork_successor(fork).precompiles())
45+
all_precompiles = successor_precompiles | predecessor_precompiles
46+
highest_precompile = int.from_bytes(max(all_precompiles))
47+
extra_range = 32
48+
extra_precompiles = {
49+
Address(i) for i in range(highest_precompile + 1, highest_precompile + extra_range)
50+
}
51+
all_precompiles = all_precompiles | extra_precompiles
52+
for address in sorted(all_precompiles):
53+
yield address, address in successor_precompiles, address in predecessor_precompiles
54+
55+
56+
@pytest.mark.valid_at_transition_to("Paris", subsequent_forks=True)
57+
@pytest.mark.parametrize_by_fork(
58+
"address,precompile_in_successor,precompile_in_predecessor",
59+
precompile_addresses_in_predecessor_successor,
60+
)
61+
def test_precompile_warming(
62+
blockchain_test: BlockchainTestFiller,
63+
fork: Fork,
64+
address: Address,
65+
precompile_in_successor: bool,
66+
precompile_in_predecessor: bool,
67+
pre: Alloc,
68+
):
69+
"""
70+
Call BALANCE of a precompile addresses before and after a fork.
71+
72+
According to EIP-2929, when a transaction begins, accessed_addresses is initialized to include:
73+
- tx.sender, tx.to
74+
- and the set of all precompiles
75+
76+
This test verifies that:
77+
1. Precompiles that exist in the predecessor fork are always "warm" (lower gas cost)
78+
2. New precompiles added in a fork are "cold" before the fork and become "warm" after
79+
80+
"""
81+
sender = pre.fund_eoa()
82+
call_cost_slot = 0
83+
84+
code = (
85+
Op.GAS
86+
+ Op.BALANCE(address)
87+
+ Op.POP
88+
+ Op.SSTORE(call_cost_slot, Op.SUB(Op.SWAP1, Op.GAS))
89+
+ Op.STOP
90+
)
91+
before = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF})
92+
after = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF})
93+
94+
# Block before fork
95+
blocks = [
96+
Block(
97+
timestamp=10_000,
98+
txs=[
99+
Transaction(
100+
sender=sender,
101+
to=before,
102+
gas_limit=1_000_000,
103+
)
104+
],
105+
)
106+
]
107+
108+
# Block after fork
109+
blocks += [
110+
Block(
111+
timestamp=20_000,
112+
txs=[
113+
Transaction(
114+
sender=sender,
115+
to=after,
116+
gas_limit=1_000_000,
117+
)
118+
],
119+
)
120+
]
121+
122+
predecessor = get_transition_fork_predecessor(fork)
123+
successor = get_transition_fork_successor(fork)
124+
125+
def get_expected_gas(precompile_present: bool, fork: Fork) -> int:
126+
gas_costs = fork.gas_costs()
127+
warm_access_cost = gas_costs.G_WARM_ACCOUNT_ACCESS
128+
cold_access_cost = gas_costs.G_COLD_ACCOUNT_ACCESS
129+
extra_cost = gas_costs.G_BASE * 2 + gas_costs.G_VERY_LOW
130+
if precompile_present:
131+
return warm_access_cost + extra_cost
132+
else:
133+
return cold_access_cost + extra_cost
134+
135+
expected_gas_before = get_expected_gas(precompile_in_predecessor, predecessor)
136+
expected_gas_after = get_expected_gas(precompile_in_successor, successor)
137+
138+
post = {
139+
before: Account(storage={call_cost_slot: expected_gas_before}),
140+
after: Account(storage={call_cost_slot: expected_gas_after}),
141+
}
142+
143+
blockchain_test(
144+
pre=pre,
145+
post=post,
146+
blocks=blocks,
147+
)

0 commit comments

Comments
 (0)