Skip to content

Commit 162778a

Browse files
jochem-brouwerspencer-tbmarioevz
authored andcommitted
feat(tests): add clz eip-7939 tests. (ethereum#1733)
Co-authored-by: spencer-tb <spencer.taylor-brown@ethereum.org> Co-authored-by: Mario Vega <marioevz@gmail.com>
1 parent 43b6217 commit 162778a

File tree

8 files changed

+289
-3
lines changed

8 files changed

+289
-3
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Users can select any of the artifacts depending on their testing needs for their
7171
-[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)).
7272
-[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).
7373
- ✨ Introduce blockchain tests for ZKEVM to cover the scenario of pure ether transfers [#1742](https://github.com/ethereum/execution-spec-tests/pull/1742).
74+
-[EIP-7939](https://eips.ethereum.org/EIPS/eip-7939) Add count leading zeros (CLZ) opcode tests for Osaka ([#1733](https://github.com/ethereum/execution-spec-tests/pull/1733)).
7475

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

eels_resolutions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
},
4242
"Osaka": {
4343
"git_url": "https://github.com/spencer-tb/execution-specs.git",
44-
"branch": "forks/osaka",
45-
"commit": "0d86ad789e7c0d25ec86f15d0e4adb9d9b308af3"
44+
"branch": "forks/osaka-devnet-berlininterop",
45+
"commit": "99734284b89766883ecb680e57b07fbca47da51b"
4646
}
4747
}

src/ethereum_test_tools/tests/test_code.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,5 +643,5 @@ def test_full_opcode_range():
643643
"""
644644
assert len(set(Op) & set(UndefinedOpcodes)) == 0
645645
full_possible_opcode_set = set(Op) | set(UndefinedOpcodes)
646-
assert len(full_possible_opcode_set) == 256
646+
assert len(full_possible_opcode_set) == 257
647647
assert {op.hex() for op in full_possible_opcode_set} == {f"{i:02x}" for i in range(256)}

src/ethereum_test_vm/opcode.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,34 @@ class Opcodes(Opcode, Enum):
11711171
Source: [evm.codes/#1D](https://www.evm.codes/#1D)
11721172
"""
11731173

1174+
CLZ = Opcode(0x1E, popped_stack_items=1, pushed_stack_items=1)
1175+
"""
1176+
CLZ(value) = count_leading_zeros(value)
1177+
----
1178+
1179+
Description
1180+
----
1181+
Counts leading zeros (bitwise).
1182+
1183+
Inputs
1184+
----
1185+
- value: integer to count zeros on
1186+
1187+
Outputs
1188+
----
1189+
- zeros: leading zero bits
1190+
1191+
Fork
1192+
----
1193+
Osaka
1194+
1195+
Gas
1196+
----
1197+
3
1198+
1199+
Source: [evm.codes/#1E](https://www.evm.codes/#1E)
1200+
"""
1201+
11741202
SHA3 = Opcode(0x20, popped_stack_items=2, pushed_stack_items=1, kwargs=["offset", "size"])
11751203
"""
11761204
SHA3(offset, size) = hash
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
abstract: Tests [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)
3+
Test cases for [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939).
4+
"""
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Defines EIP-7939 specification constants and functions."""
2+
3+
from dataclasses import dataclass
4+
5+
6+
@dataclass(frozen=True)
7+
class ReferenceSpec:
8+
"""Defines the reference spec version and git path."""
9+
10+
git_path: str
11+
version: str
12+
13+
14+
ref_spec_7939 = ReferenceSpec("EIPS/eip-7939.md", "c8321494fdfbfda52ad46c3515a7ca5dc86b857c")
15+
16+
17+
@dataclass(frozen=True)
18+
class Spec:
19+
"""Constants and helpers for the CLZ opcode."""
20+
21+
CLZ_GAS_COST = 3
22+
23+
@classmethod
24+
def calculate_clz(cls, value: int) -> int:
25+
"""Calculate the count of leading zeros for a 256-bit value."""
26+
if value == 0:
27+
return 256
28+
return 256 - value.bit_length()
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
"""
2+
abstract: Tests [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)
3+
Test cases for [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939).
4+
"""
5+
6+
import pytest
7+
8+
from ethereum_test_forks import Fork
9+
from ethereum_test_tools import (
10+
Account,
11+
Alloc,
12+
Block,
13+
BlockchainTestFiller,
14+
CodeGasMeasure,
15+
StateTestFiller,
16+
Transaction,
17+
)
18+
from ethereum_test_tools.vm.opcode import Opcodes as Op
19+
20+
from .spec import Spec, ref_spec_7939
21+
22+
REFERENCE_SPEC_GIT_PATH = ref_spec_7939.git_path
23+
REFERENCE_SPEC_VERSION = ref_spec_7939.version
24+
25+
26+
def clz_parameters():
27+
"""Generate all test case parameters."""
28+
test_cases = []
29+
30+
# Format 0xb000...111: leading zeros followed by ones
31+
# Special case: bits=256 gives value=0 (all zeros)
32+
for bits in range(257):
33+
value = (2**256 - 1) >> bits
34+
expected_clz = bits
35+
assert expected_clz == Spec.calculate_clz(value), (
36+
f"CLZ calculation mismatch for leading_zeros_{bits}: "
37+
f"manual={expected_clz}, spec={Spec.calculate_clz(value)}, value={hex(value)}"
38+
)
39+
test_cases.append((f"leading_zeros_{bits}", value, expected_clz))
40+
41+
# Format 0xb010...000: single bit set
42+
for bits in range(256):
43+
value = 1 << bits
44+
expected_clz = 255 - bits
45+
assert expected_clz == Spec.calculate_clz(value), (
46+
f"CLZ calculation mismatch for single_bit_{bits}: "
47+
f"manual={expected_clz}, spec={Spec.calculate_clz(value)}, value={hex(value)}"
48+
)
49+
test_cases.append((f"single_bit_{bits}", value, expected_clz))
50+
51+
# Arbitrary edge cases
52+
arbitrary_values = [
53+
0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0,
54+
0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF,
55+
0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,
56+
0xDEADBEEFCAFEBABE0123456789ABCDEF,
57+
0x0123456789ABCDEF,
58+
(1 << 128) + 1,
59+
(1 << 200) + (1 << 100),
60+
2**255 - 1,
61+
]
62+
for i, value in enumerate(arbitrary_values):
63+
expected_clz = Spec.calculate_clz(value)
64+
test_cases.append((f"arbitrary_{i}", value, expected_clz))
65+
66+
return test_cases
67+
68+
69+
@pytest.mark.valid_from("Osaka")
70+
@pytest.mark.parametrize(
71+
"test_id,value,expected_clz",
72+
clz_parameters(),
73+
ids=[f"{test_data[0]}-expected_clz_{test_data[2]}" for test_data in clz_parameters()],
74+
)
75+
def test_clz_opcode_scenarios(
76+
state_test: StateTestFiller,
77+
pre: Alloc,
78+
test_id: str,
79+
value: int,
80+
expected_clz: int,
81+
):
82+
"""
83+
Test CLZ opcode functionality.
84+
85+
Cases:
86+
- Format 0xb000...111: leading zeros followed by ones (2**256 - 1 >> bits)
87+
- Format 0xb010...000: single bit set at position (1 << bits)
88+
89+
Test coverage:
90+
- Leading zeros pattern: 0b000...111 (0 to 256 leading zeros)
91+
- Single bit pattern: 0b010...000 (bit at each possible position)
92+
- Edge cases: CLZ(0) = 256, CLZ(2^256-1) = 0
93+
"""
94+
sender = pre.fund_eoa()
95+
contract_address = pre.deploy_contract(
96+
code=Op.SSTORE(0, Op.CLZ(value)),
97+
storage={"0x00": "0xdeadbeef"},
98+
)
99+
tx = Transaction(
100+
to=contract_address,
101+
sender=sender,
102+
gas_limit=200_000,
103+
)
104+
post = {
105+
contract_address: Account(storage={"0x00": expected_clz}),
106+
}
107+
state_test(pre=pre, post=post, tx=tx)
108+
109+
110+
@pytest.mark.valid_from("Osaka")
111+
def test_clz_gas(state_test: StateTestFiller, pre: Alloc, fork: Fork):
112+
"""Test CLZ opcode gas cost."""
113+
contract_address = pre.deploy_contract(
114+
Op.SSTORE(
115+
0,
116+
CodeGasMeasure(
117+
code=Op.CLZ(Op.PUSH1(1)),
118+
extra_stack_items=1,
119+
overhead_cost=fork.gas_costs().G_VERY_LOW,
120+
),
121+
),
122+
storage={"0x00": "0xdeadbeef"},
123+
)
124+
sender = pre.fund_eoa()
125+
tx = Transaction(to=contract_address, sender=sender, gas_limit=200_000)
126+
post = {
127+
contract_address: Account( # Cost measured is CLZ + PUSH1
128+
storage={"0x00": fork.gas_costs().G_VERY_LOW}
129+
),
130+
}
131+
state_test(pre=pre, post=post, tx=tx)
132+
133+
134+
@pytest.mark.valid_from("Osaka")
135+
def test_clz_stack_underflow(state_test: StateTestFiller, pre: Alloc):
136+
"""Test CLZ opcode with empty stack (should revert due to stack underflow)."""
137+
sender = pre.fund_eoa()
138+
callee_address = pre.deploy_contract(
139+
code=Op.CLZ + Op.STOP, # No stack items, should underflow
140+
)
141+
caller_address = pre.deploy_contract(
142+
code=Op.SSTORE(0, Op.CALL(gas=0xFFFF, address=callee_address)),
143+
storage={"0x00": "0xdeadbeef"},
144+
)
145+
tx = Transaction(
146+
to=caller_address,
147+
sender=sender,
148+
gas_limit=200_000,
149+
)
150+
post = {
151+
caller_address: Account(
152+
storage={"0x00": 0} # Call failed due to stack underflow
153+
),
154+
}
155+
state_test(pre=pre, post=post, tx=tx)
156+
157+
158+
@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True)
159+
def test_clz_fork_transition(blockchain_test: BlockchainTestFiller, pre: Alloc):
160+
"""Test CLZ opcode behavior at fork transition."""
161+
sender = pre.fund_eoa()
162+
callee_address = pre.deploy_contract(
163+
code=Op.SSTORE(Op.NUMBER, Op.CLZ(1 << 100)) + Op.STOP,
164+
storage={"0x00": "0xdeadbeef"},
165+
)
166+
caller_address = pre.deploy_contract(
167+
code=Op.SSTORE(Op.NUMBER, Op.CALL(gas=0xFFFF, address=callee_address)),
168+
storage={"0x00": "0xdeadbeef"},
169+
)
170+
blocks = [
171+
Block(
172+
timestamp=14_999,
173+
txs=[
174+
Transaction(
175+
to=caller_address,
176+
sender=sender,
177+
nonce=0,
178+
gas_limit=200_000,
179+
)
180+
],
181+
),
182+
Block(
183+
timestamp=15_000,
184+
txs=[
185+
Transaction(
186+
to=caller_address,
187+
sender=sender,
188+
nonce=1,
189+
gas_limit=200_000,
190+
)
191+
],
192+
),
193+
Block(
194+
timestamp=15_001,
195+
txs=[
196+
Transaction(
197+
to=caller_address,
198+
sender=sender,
199+
nonce=2,
200+
gas_limit=200_000,
201+
)
202+
],
203+
),
204+
]
205+
blockchain_test(
206+
pre=pre,
207+
blocks=blocks,
208+
post={
209+
caller_address: Account(
210+
storage={
211+
14_999: 0,
212+
15_000: 1,
213+
15_001: 1,
214+
}
215+
),
216+
callee_address: Account(
217+
storage={
218+
14_999: 155,
219+
15_000: 155,
220+
15_001: 155,
221+
}
222+
),
223+
},
224+
)

whitelist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,3 +1068,4 @@ nagydani
10681068
guido
10691069
marcin
10701070
codespell
1071+
CLZ

0 commit comments

Comments
 (0)