Skip to content

feat(tests): add CLZ opcode EIP-7939) tests #1733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Users can select any of the artifacts depending on their testing needs for their
- ✨ [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)).
- ✨ [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).
- ✨ Introduce blockchain tests for ZKEVM to cover the scenario of pure ether transfers [#1742](https://github.com/ethereum/execution-spec-tests/pull/1742).
- ✨ [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)).

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

Expand Down
4 changes: 2 additions & 2 deletions eels_resolutions.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
"Osaka": {
"git_url": "https://github.com/spencer-tb/execution-specs.git",
"branch": "forks/osaka",
"commit": "0d86ad789e7c0d25ec86f15d0e4adb9d9b308af3"
"branch": "forks/osaka-devnet-berlininterop",
"commit": "99734284b89766883ecb680e57b07fbca47da51b"
}
}
2 changes: 1 addition & 1 deletion src/ethereum_test_tools/tests/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,5 +643,5 @@ def test_full_opcode_range():
"""
assert len(set(Op) & set(UndefinedOpcodes)) == 0
full_possible_opcode_set = set(Op) | set(UndefinedOpcodes)
assert len(full_possible_opcode_set) == 256
assert len(full_possible_opcode_set) == 257
assert {op.hex() for op in full_possible_opcode_set} == {f"{i:02x}" for i in range(256)}
28 changes: 28 additions & 0 deletions src/ethereum_test_vm/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,34 @@ class Opcodes(Opcode, Enum):
Source: [evm.codes/#1D](https://www.evm.codes/#1D)
"""

CLZ = Opcode(0x1E, popped_stack_items=1, pushed_stack_items=1)
"""
CLZ(value) = count_leading_zeros(value)
----

Description
----
Counts leading zeros (bitwise).

Inputs
----
- value: integer to count zeros on

Outputs
----
- zeros: leading zero bits

Fork
----
Osaka

Gas
----
3

Source: [evm.codes/#1E](https://www.evm.codes/#1E)
"""

SHA3 = Opcode(0x20, popped_stack_items=2, pushed_stack_items=1, kwargs=["offset", "size"])
"""
SHA3(offset, size) = hash
Expand Down
4 changes: 4 additions & 0 deletions tests/osaka/eip7939_count_leading_zeros/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
abstract: Tests [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)
Test cases for [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939).
"""
28 changes: 28 additions & 0 deletions tests/osaka/eip7939_count_leading_zeros/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Defines EIP-7939 specification constants and functions."""

from dataclasses import dataclass


@dataclass(frozen=True)
class ReferenceSpec:
"""Defines the reference spec version and git path."""

git_path: str
version: str


ref_spec_7939 = ReferenceSpec("EIPS/eip-7939.md", "c8321494fdfbfda52ad46c3515a7ca5dc86b857c")


@dataclass(frozen=True)
class Spec:
"""Constants and helpers for the CLZ opcode."""

CLZ_GAS_COST = 3

@classmethod
def calculate_clz(cls, value: int) -> int:
"""Calculate the count of leading zeros for a 256-bit value."""
if value == 0:
return 256
return 256 - value.bit_length()
224 changes: 224 additions & 0 deletions tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
"""
abstract: Tests [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)
Test cases for [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939).
"""

import pytest

from ethereum_test_forks import Fork
from ethereum_test_tools import (
Account,
Alloc,
Block,
BlockchainTestFiller,
CodeGasMeasure,
StateTestFiller,
Transaction,
)
from ethereum_test_tools.vm.opcode import Opcodes as Op

from .spec import Spec, ref_spec_7939

REFERENCE_SPEC_GIT_PATH = ref_spec_7939.git_path
REFERENCE_SPEC_VERSION = ref_spec_7939.version


def clz_parameters():
"""Generate all test case parameters."""
test_cases = []

# Format 0xb000...111: leading zeros followed by ones
# Special case: bits=256 gives value=0 (all zeros)
for bits in range(257):
value = (2**256 - 1) >> bits
expected_clz = bits
assert expected_clz == Spec.calculate_clz(value), (
f"CLZ calculation mismatch for leading_zeros_{bits}: "
f"manual={expected_clz}, spec={Spec.calculate_clz(value)}, value={hex(value)}"
)
test_cases.append((f"leading_zeros_{bits}", value, expected_clz))

# Format 0xb010...000: single bit set
for bits in range(256):
value = 1 << bits
expected_clz = 255 - bits
assert expected_clz == Spec.calculate_clz(value), (
f"CLZ calculation mismatch for single_bit_{bits}: "
f"manual={expected_clz}, spec={Spec.calculate_clz(value)}, value={hex(value)}"
)
test_cases.append((f"single_bit_{bits}", value, expected_clz))

# Arbitrary edge cases
arbitrary_values = [
0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0,
0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF,
0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F,
0xDEADBEEFCAFEBABE0123456789ABCDEF,
0x0123456789ABCDEF,
(1 << 128) + 1,
(1 << 200) + (1 << 100),
2**255 - 1,
]
for i, value in enumerate(arbitrary_values):
expected_clz = Spec.calculate_clz(value)
test_cases.append((f"arbitrary_{i}", value, expected_clz))

return test_cases


@pytest.mark.valid_from("Osaka")
@pytest.mark.parametrize(
"test_id,value,expected_clz",
clz_parameters(),
ids=[f"{test_data[0]}-expected_clz_{test_data[2]}" for test_data in clz_parameters()],
)
def test_clz_opcode_scenarios(
state_test: StateTestFiller,
pre: Alloc,
test_id: str,
value: int,
expected_clz: int,
):
"""
Test CLZ opcode functionality.

Cases:
- Format 0xb000...111: leading zeros followed by ones (2**256 - 1 >> bits)
- Format 0xb010...000: single bit set at position (1 << bits)

Test coverage:
- Leading zeros pattern: 0b000...111 (0 to 256 leading zeros)
- Single bit pattern: 0b010...000 (bit at each possible position)
- Edge cases: CLZ(0) = 256, CLZ(2^256-1) = 0
"""
sender = pre.fund_eoa()
contract_address = pre.deploy_contract(
code=Op.SSTORE(0, Op.CLZ(value)),
storage={"0x00": "0xdeadbeef"},
)
tx = Transaction(
to=contract_address,
sender=sender,
gas_limit=200_000,
)
post = {
contract_address: Account(storage={"0x00": expected_clz}),
}
state_test(pre=pre, post=post, tx=tx)


@pytest.mark.valid_from("Osaka")
def test_clz_gas(state_test: StateTestFiller, pre: Alloc, fork: Fork):
"""Test CLZ opcode gas cost."""
contract_address = pre.deploy_contract(
Op.SSTORE(
0,
CodeGasMeasure(
code=Op.CLZ(Op.PUSH1(1)),
extra_stack_items=1,
overhead_cost=fork.gas_costs().G_VERY_LOW,
),
),
storage={"0x00": "0xdeadbeef"},
)
sender = pre.fund_eoa()
tx = Transaction(to=contract_address, sender=sender, gas_limit=200_000)
post = {
contract_address: Account( # Cost measured is CLZ + PUSH1
storage={"0x00": fork.gas_costs().G_VERY_LOW}
),
}
state_test(pre=pre, post=post, tx=tx)


@pytest.mark.valid_from("Osaka")
def test_clz_stack_underflow(state_test: StateTestFiller, pre: Alloc):
"""Test CLZ opcode with empty stack (should revert due to stack underflow)."""
sender = pre.fund_eoa()
callee_address = pre.deploy_contract(
code=Op.CLZ + Op.STOP, # No stack items, should underflow
)
caller_address = pre.deploy_contract(
code=Op.SSTORE(0, Op.CALL(gas=0xFFFF, address=callee_address)),
storage={"0x00": "0xdeadbeef"},
)
tx = Transaction(
to=caller_address,
sender=sender,
gas_limit=200_000,
)
post = {
caller_address: Account(
storage={"0x00": 0} # Call failed due to stack underflow
),
}
state_test(pre=pre, post=post, tx=tx)


@pytest.mark.valid_at_transition_to("Osaka", subsequent_forks=True)
def test_clz_fork_transition(blockchain_test: BlockchainTestFiller, pre: Alloc):
"""Test CLZ opcode behavior at fork transition."""
sender = pre.fund_eoa()
callee_address = pre.deploy_contract(
code=Op.SSTORE(Op.NUMBER, Op.CLZ(1 << 100)) + Op.STOP,
storage={"0x00": "0xdeadbeef"},
)
caller_address = pre.deploy_contract(
code=Op.SSTORE(Op.NUMBER, Op.CALL(gas=0xFFFF, address=callee_address)),
storage={"0x00": "0xdeadbeef"},
)
blocks = [
Block(
timestamp=14_999,
txs=[
Transaction(
to=caller_address,
sender=sender,
nonce=0,
gas_limit=200_000,
)
],
),
Block(
timestamp=15_000,
txs=[
Transaction(
to=caller_address,
sender=sender,
nonce=1,
gas_limit=200_000,
)
],
),
Block(
timestamp=15_001,
txs=[
Transaction(
to=caller_address,
sender=sender,
nonce=2,
gas_limit=200_000,
)
],
),
]
blockchain_test(
pre=pre,
blocks=blocks,
post={
caller_address: Account(
storage={
14_999: 0,
15_000: 1,
15_001: 1,
}
),
callee_address: Account(
storage={
14_999: 155,
15_000: 155,
15_001: 155,
}
),
},
)
1 change: 1 addition & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1068,3 +1068,4 @@ nagydani
guido
marcin
codespell
CLZ
Loading