Skip to content

Commit c2bf887

Browse files
authored
Merge branch 'master' into chore/upgrade-noble-2.0.1
2 parents d46b971 + e35961c commit c2bf887

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
})

packages/evm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"coverage": "DEBUG=ethjs npx vitest run -c ../../config/vitest.config.coverage.mts",
3737
"coverage:istanbul": "DEBUG=ethjs npx vitest run -c ../../config/vitest.config.coverage.istanbul.mts",
3838
"docs:build": "typedoc --options typedoc.mjs",
39-
"examples": "tsx ../../scripts/examples-runner.ts -- evm && tsx ../../scripts/examples-runner.ts -- evm precompiles",
39+
"examples": "tsx ../../scripts/examples-runner.ts -- evm && tsx ../../scripts/examples-runner.ts -- evm precompiles && tsx ../../scripts/examples-runner.ts -- evm opcodes",
4040
"examples:build": "npx embedme README.md",
4141
"formatTest": "node ./scripts/formatTest",
4242
"lint": "npm run biome && eslint --config ./eslint.config.mjs .",

0 commit comments

Comments
 (0)