Skip to content

Commit 2f66dcc

Browse files
feat(tests): add EIP-7951 for secp256r1 curve (#1670)
* feat: configure test env for osaka * feat: add tests for rip-7212 to support secp256r1 * test: add gas requirement and call type test * test(P256VERIFY): add fork transition tests * test: use precompile as set code delegated address * test: add precompile as tx entry point * refactor: add eip checklist marker * refactor: update rip7212 to eip7951 * feat: add curve params and negative tests * refactor: update precompile as set code delegated address * fix: update bounded value to include precompile * doc: add comments for external test vectors * docs: update changelog * refactor: rename filename * fix: update frontier configuration * Revert "fix: update frontier configuration" This reverts commit 44a62c1. * refactor: improve precompile address handling in tests * fix: tox --------- Co-authored-by: Mario Vega <[email protected]>
1 parent 2f2c48f commit 2f66dcc

File tree

12 files changed

+6120
-13
lines changed

12 files changed

+6120
-13
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Users can select any of the artifacts depending on their testing needs for their
6464
- 🔀 Refactored EIP-145 static tests into python ([#1683](https://github.com/ethereum/execution-spec-tests/pull/1683)).
6565
- ✨ EIP-7823, EIP-7883: Add test cases for ModExp precompile gas-cost updates and input limits on Osaka ([#1579](https://github.com/ethereum/execution-spec-tests/pull/1579), [#1729](https://github.com/ethereum/execution-spec-tests/pull/1729)).
6666
-[EIP-7825](https://eips.ethereum.org/EIPS/eip-7825): Add test cases for the transaction gas limit of 30M gas ([#1711](https://github.com/ethereum/execution-spec-tests/pull/1711)).
67+
-[EIP-7951](https://eips.ethereum.org/EIPS/eip-7951): add test cases for `P256VERIFY` precompile to support secp256r1 curve [#1670](https://github.com/ethereum/execution-spec-tests/pull/1670)
6768

6869
## [v4.5.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0) - 2025-05-14
6970

src/ethereum_test_forks/forks/forks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,15 @@ def solc_min_version(cls) -> Version:
13621362
"""Return minimum version of solc that supports this fork."""
13631363
return Version.parse("1.0.0") # set a high version; currently unknown
13641364

1365+
@classmethod
1366+
def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address]:
1367+
"""
1368+
At Osaka, pre-compile for p256verify operation is added.
1369+
1370+
P256VERIFY = 0x100
1371+
"""
1372+
return [Address(0x100)] + super(Osaka, cls).precompiles(block_number, timestamp)
1373+
13651374

13661375
class EOFv1(Prague, solc_name="cancun"):
13671376
"""EOF fork."""

src/ethereum_test_forks/tests/test_forks.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Homestead,
1616
Istanbul,
1717
London,
18+
Osaka,
1819
Paris,
1920
Prague,
2021
Shanghai,
@@ -23,6 +24,7 @@
2324
BerlinToLondonAt5,
2425
CancunToPragueAtTime15k,
2526
ParisToShanghaiAtTime15k,
27+
PragueToOsakaAtTime15k,
2628
ShanghaiToCancunAtTime15k,
2729
)
2830
from ..helpers import (
@@ -223,6 +225,7 @@ def test_transition_fork_comparison():
223225

224226
assert sorted(
225227
{
228+
PragueToOsakaAtTime15k,
226229
CancunToPragueAtTime15k,
227230
ParisToShanghaiAtTime15k,
228231
ShanghaiToCancunAtTime15k,
@@ -233,6 +236,7 @@ def test_transition_fork_comparison():
233236
ParisToShanghaiAtTime15k,
234237
ShanghaiToCancunAtTime15k,
235238
CancunToPragueAtTime15k,
239+
PragueToOsakaAtTime15k,
236240
]
237241

238242

@@ -349,7 +353,7 @@ def test_tx_intrinsic_gas_functions(fork: Fork, calldata: bytes, create_tx: bool
349353
)
350354

351355

352-
class FutureFork(Prague):
356+
class FutureFork(Osaka):
353357
"""
354358
Dummy fork used for testing.
355359
@@ -391,6 +395,27 @@ class FutureFork(Prague):
391395
},
392396
id="Prague",
393397
),
398+
pytest.param(
399+
Osaka,
400+
{
401+
"Cancun": {
402+
"target_blobs_per_block": 3,
403+
"max_blobs_per_block": 6,
404+
"baseFeeUpdateFraction": 3338477,
405+
},
406+
"Prague": {
407+
"target_blobs_per_block": 6,
408+
"max_blobs_per_block": 9,
409+
"baseFeeUpdateFraction": 5007716,
410+
},
411+
"Osaka": {
412+
"target_blobs_per_block": 6,
413+
"max_blobs_per_block": 9,
414+
"baseFeeUpdateFraction": 5007716,
415+
},
416+
},
417+
id="Osaka",
418+
),
394419
pytest.param(
395420
CancunToPragueAtTime15k,
396421
{
@@ -407,6 +432,27 @@ class FutureFork(Prague):
407432
},
408433
id="CancunToPragueAtTime15k",
409434
),
435+
pytest.param(
436+
PragueToOsakaAtTime15k,
437+
{
438+
"Cancun": {
439+
"target_blobs_per_block": 3,
440+
"max_blobs_per_block": 6,
441+
"baseFeeUpdateFraction": 3338477,
442+
},
443+
"Prague": {
444+
"target_blobs_per_block": 6,
445+
"max_blobs_per_block": 9,
446+
"baseFeeUpdateFraction": 5007716,
447+
},
448+
"Osaka": {
449+
"target_blobs_per_block": 6,
450+
"max_blobs_per_block": 9,
451+
"baseFeeUpdateFraction": 5007716,
452+
},
453+
},
454+
id="PragueToOsakaAtTime15k",
455+
),
410456
pytest.param(
411457
FutureFork,
412458
{
@@ -420,6 +466,11 @@ class FutureFork(Prague):
420466
"max_blobs_per_block": 9,
421467
"baseFeeUpdateFraction": 5007716,
422468
},
469+
"Osaka": {
470+
"target_blobs_per_block": 6,
471+
"max_blobs_per_block": 9,
472+
"baseFeeUpdateFraction": 5007716,
473+
},
423474
"FutureFork": {
424475
"target_blobs_per_block": 6,
425476
"max_blobs_per_block": 9,

tests/frontier/precompiles/test_precompiles.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@
77
from ethereum_test_forks import Fork
88
from ethereum_test_tools import (
99
Account,
10+
Address,
1011
Alloc,
1112
Environment,
1213
StateTestFiller,
1314
Transaction,
1415
)
1516
from ethereum_test_tools.vm.opcode import Opcodes as Op
1617

17-
UPPER_BOUND = 0xFF
18-
NUM_UNSUPPORTED_PRECOMPILES = 1
1918

20-
21-
def precompile_addresses(fork: Fork) -> Iterator[Tuple[str, bool]]:
19+
def precompile_addresses(fork: Fork) -> Iterator[Tuple[Address, bool]]:
2220
"""
2321
Yield the addresses of precompiled contracts and their support status for a given fork.
2422
@@ -32,14 +30,13 @@ def precompile_addresses(fork: Fork) -> Iterator[Tuple[str, bool]]:
3230
"""
3331
supported_precompiles = fork.precompiles()
3432

35-
num_unsupported = NUM_UNSUPPORTED_PRECOMPILES
36-
for address in range(1, UPPER_BOUND + 1):
37-
if address in supported_precompiles:
38-
yield (hex(address), True)
39-
elif num_unsupported > 0:
40-
# Check unsupported precompiles up to NUM_UNSUPPORTED_PRECOMPILES
41-
yield (hex(address), False)
42-
num_unsupported -= 1
33+
for address in supported_precompiles:
34+
address_int = int.from_bytes(address, byteorder="big")
35+
yield (address, True)
36+
if address_int > 0 and (address_int - 1) not in supported_precompiles:
37+
yield (Address(address_int - 1), False)
38+
if (address_int + 1) not in supported_precompiles:
39+
yield (Address(address_int + 1), False)
4340

4441

4542
@pytest.mark.ported_from(
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
abstract: Tests [EIP-7951: Precompile for secp256r1 Curve Support](https://eips.ethereum.org/EIPS/eip-7951)
3+
Test cases for [EIP-7951: Precompile for secp256r1 Curve Support](https://eips.ethereum.org/EIPS/eip-7951)].
4+
"""
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""Shared pytest definitions local to EIP-7951 tests."""
2+
3+
from typing import SupportsBytes
4+
5+
import pytest
6+
7+
from ethereum_test_forks import Fork
8+
from ethereum_test_tools import EOA, Address, Alloc, Bytecode, Storage, Transaction, keccak256
9+
from ethereum_test_tools import Opcodes as Op
10+
11+
from .spec import Spec
12+
13+
14+
@pytest.fixture
15+
def vector_gas_value() -> int | None:
16+
"""
17+
Gas value from the test vector if any.
18+
19+
If `None` it means that the test scenario did not come from a file, so no comparison is needed.
20+
21+
The `vectors_from_file` function reads the gas value from the file and overwrites this fixture.
22+
"""
23+
return None
24+
25+
26+
@pytest.fixture
27+
def precompile_gas(vector_gas_value: int | None) -> int:
28+
"""Gas cost for the precompile."""
29+
if vector_gas_value is not None:
30+
assert vector_gas_value == Spec.P256VERIFY_GAS, (
31+
f"Calculated gas {vector_gas_value} != Vector gas {Spec.P256VERIFY_GAS}"
32+
)
33+
return Spec.P256VERIFY_GAS
34+
35+
36+
@pytest.fixture
37+
def precompile_gas_modifier() -> int:
38+
"""
39+
Modify the gas passed to the precompile, for testing purposes.
40+
41+
By default the call is made with the exact gas amount required for the given opcode,
42+
but when this fixture is overridden, the gas amount can be modified to, e.g., test
43+
a lower amount and test if the precompile call fails.
44+
"""
45+
return 0
46+
47+
48+
@pytest.fixture
49+
def call_opcode() -> Op:
50+
"""
51+
Type of call used to call the precompile.
52+
53+
By default it is Op.CALL, but it can be overridden in the test.
54+
"""
55+
return Op.CALL
56+
57+
58+
@pytest.fixture
59+
def call_contract_post_storage() -> Storage:
60+
"""
61+
Storage of the test contract after the transaction is executed.
62+
Note: Fixture `call_contract_code` fills the actual expected storage values.
63+
"""
64+
return Storage()
65+
66+
67+
@pytest.fixture
68+
def call_succeeds() -> bool:
69+
"""
70+
By default, depending on the expected output, we can deduce if the call is expected to succeed
71+
or fail.
72+
"""
73+
return True
74+
75+
76+
@pytest.fixture
77+
def call_contract_code(
78+
precompile_address: int,
79+
precompile_gas: int,
80+
precompile_gas_modifier: int,
81+
expected_output: bytes | SupportsBytes,
82+
call_succeeds: bool,
83+
call_opcode: Op,
84+
call_contract_post_storage: Storage,
85+
) -> Bytecode:
86+
"""Code of the test contract."""
87+
expected_output = bytes(expected_output)
88+
assert call_opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]
89+
value = [0] if call_opcode in [Op.CALL, Op.CALLCODE] else []
90+
91+
code = Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.SSTORE(
92+
call_contract_post_storage.store_next(call_succeeds),
93+
call_opcode(
94+
precompile_gas + precompile_gas_modifier,
95+
precompile_address,
96+
*value,
97+
0,
98+
Op.CALLDATASIZE(),
99+
0,
100+
0,
101+
)
102+
+ Op.SSTORE(
103+
call_contract_post_storage.store_next(len(expected_output)), Op.RETURNDATASIZE()
104+
),
105+
)
106+
if call_succeeds:
107+
# Add integrity check only if the call is expected to succeed.
108+
code += Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE()) + Op.SSTORE(
109+
call_contract_post_storage.store_next(keccak256(expected_output)),
110+
Op.SHA3(0, Op.RETURNDATASIZE()),
111+
)
112+
return code
113+
114+
115+
@pytest.fixture
116+
def call_contract_address(pre: Alloc, call_contract_code: Bytecode) -> Address:
117+
"""Address where the test contract will be deployed."""
118+
return pre.deploy_contract(call_contract_code)
119+
120+
121+
@pytest.fixture
122+
def sender(pre: Alloc) -> EOA:
123+
"""Sender of the transaction."""
124+
return pre.fund_eoa()
125+
126+
127+
@pytest.fixture
128+
def post(call_contract_address: Address, call_contract_post_storage: Storage):
129+
"""Test expected post outcome."""
130+
return {
131+
call_contract_address: {
132+
"storage": call_contract_post_storage,
133+
},
134+
}
135+
136+
137+
@pytest.fixture
138+
def tx_gas_limit(fork: Fork, input_data: bytes, precompile_gas: int) -> int:
139+
"""Transaction gas limit used for the test (Can be overridden in the test)."""
140+
intrinsic_gas_cost_calculator = fork.transaction_intrinsic_cost_calculator()
141+
memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator()
142+
extra_gas = 100_000
143+
return (
144+
extra_gas
145+
+ intrinsic_gas_cost_calculator(calldata=input_data)
146+
+ memory_expansion_gas_calculator(new_bytes=len(input_data))
147+
+ precompile_gas
148+
)
149+
150+
151+
@pytest.fixture
152+
def tx(
153+
input_data: bytes,
154+
tx_gas_limit: int,
155+
call_contract_address: Address,
156+
sender: EOA,
157+
) -> Transaction:
158+
"""Transaction for the test."""
159+
return Transaction(
160+
gas_limit=tx_gas_limit, data=input_data, to=call_contract_address, sender=sender
161+
)

0 commit comments

Comments
 (0)