|
| 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 | + ) |
0 commit comments