|
| 1 | +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' |
| 2 | +import { type EVM, createEVM } from '@ethereumjs/evm' |
| 3 | +import { type PrefixedHexString, hexToBytes } from '@ethereumjs/util' |
| 4 | + |
| 5 | +// CLZ (Count Leading Zeros) opcode (0x1e) |
| 6 | +// Demonstrates the CLZ opcode introduced in https://eips.ethereum.org/EIPS/eip-7939 |
| 7 | +// |
| 8 | +// The CLZ opcode returns the number of zero bits before the most significant 1-bit in a 256-bit value. |
| 9 | +// It enables efficient computation of bit length, log₂, and prefix comparisons directly in the EVM. |
| 10 | +// |
| 11 | +// Doing this in Solidity/Yul requires a loop or binary search using shifts and comparisons, |
| 12 | +// which costs hundreds of gas and bloats bytecode. By replacing multi-step shift and branch logic |
| 13 | +// with a single instruction, it reduces gas cost, bytecode size, and zk-proof complexity. |
| 14 | + |
| 15 | +const common = new Common({ |
| 16 | + chain: Mainnet, |
| 17 | + hardfork: Hardfork.Osaka, |
| 18 | +}) |
| 19 | + |
| 20 | +const assembleCode = (x: Uint8Array) => { |
| 21 | + if (x.length !== 32) { |
| 22 | + throw new Error('x must be 32 bytes') |
| 23 | + } |
| 24 | + |
| 25 | + const code = new Uint8Array(35) // PUSH32 + 32-byte operand + CLZ + STOP |
| 26 | + code[0] = 0x7f |
| 27 | + code.set(x, 1) |
| 28 | + code[33] = 0x1e |
| 29 | + code[34] = 0x00 |
| 30 | + return code |
| 31 | +} |
| 32 | + |
| 33 | +const runCase = async (evm: EVM, x: PrefixedHexString) => { |
| 34 | + const code = assembleCode(hexToBytes(x)) |
| 35 | + |
| 36 | + const res = await evm.runCode({ code }) |
| 37 | + |
| 38 | + const stack = res.runState?.stack |
| 39 | + if (!stack) { |
| 40 | + throw new Error('Missing runState stack in result') |
| 41 | + } |
| 42 | + |
| 43 | + const [top] = stack.peek(1) |
| 44 | + const hexValue = `0x${top.toString(16)}` |
| 45 | + const gas = res.executionGasUsed |
| 46 | + console.log('--------------------------------') |
| 47 | + console.log(`input=${x}`) |
| 48 | + console.log(`output=${hexValue} (leading zeros=${top})`) |
| 49 | + console.log(`Gas used: ${gas}`) |
| 50 | +} |
| 51 | + |
| 52 | +const main = async () => { |
| 53 | + const evm = await createEVM({ common }) |
| 54 | + |
| 55 | + // Case 1: x == 0x00..00 -> expect 256 |
| 56 | + await runCase(evm, `0x${'00'.repeat(32)}` as PrefixedHexString) |
| 57 | + |
| 58 | + // Case 2: x == 0x0..01 -> MSB at bit 0 -> expect 255 |
| 59 | + await runCase(evm, `0x${'00'.repeat(31)}01` as PrefixedHexString) |
| 60 | + |
| 61 | + // Case 3: x == 0x40..00 -> MSB at bit 254 -> expect 1 |
| 62 | + await runCase(evm, `0x40${'00'.repeat(31)}` as PrefixedHexString) |
| 63 | + |
| 64 | + // Case 4: x == 0x80..00 -> MSB at bit 255 -> expect 0 |
| 65 | + await runCase(evm, `0x80${'00'.repeat(31)}` as PrefixedHexString) |
| 66 | + console.log('--------------------------------') |
| 67 | +} |
| 68 | + |
| 69 | +void main().catch((err) => { |
| 70 | + console.error(err) |
| 71 | + process.exitCode = 1 |
| 72 | +}) |
0 commit comments