From 8ceb678c9425891ba8e3c6fef27126eb94e7af57 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Fri, 7 Nov 2025 15:42:54 -0700 Subject: [PATCH 01/10] evm: add clz example script --- packages/evm/examples/clz.ts | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/evm/examples/clz.ts diff --git a/packages/evm/examples/clz.ts b/packages/evm/examples/clz.ts new file mode 100644 index 0000000000..ddd5b82a70 --- /dev/null +++ b/packages/evm/examples/clz.ts @@ -0,0 +1,61 @@ +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { type EVM, createEVM } from '@ethereumjs/evm' +import { type PrefixedHexString, hexToBytes } from '@ethereumjs/util' + +const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.Osaka, + eips: [7939], +}) + +const assembleCode = (x: Uint8Array) => { + if (x.length !== 32) { + throw new Error('x must be 32 bytes') + } + + const code = new Uint8Array(35) // PUSH32 + 32-byte operand + CLZ + STOP + code[0] = 0x7f + code.set(x, 1) + code[33] = 0x1e + code[34] = 0x00 + return code +} + +const runCase = async (evm: EVM, x: PrefixedHexString) => { + const code = assembleCode(hexToBytes(x)) + + const res = await evm.runCode({ + code, + gasLimit: 1_000_000n, + }) + + const stack = res.runState?.stack + if (!stack) { + throw new Error('Missing runState stack in result') + } + + const [top] = stack.peek(1) + const hexValue = `0x${top.toString(16)}` + console.log(`clz=${top} (hex=${hexValue}) for x=${x}`) +} + +const main = async () => { + const evm = await createEVM({ common }) + + // Case 1: x == 0x00..00 -> expect 256 + await runCase(evm, `0x${'00'.repeat(32)}` as PrefixedHexString) + + // Case 2: x == 0x01..00 -> MSB at bit 255 -> expect 255 + await runCase(evm, `0x${'00'.repeat(31)}01` as PrefixedHexString) + + // Case 3: x == 0x40..00 -> MSB at bit 254 -> expect 1 + await runCase(evm, `0x40${'00'.repeat(31)}` as PrefixedHexString) + + // Case 4: x == 0x80..00 -> MSB at bit 255 -> expect 0 + await runCase(evm, `0x80${'00'.repeat(31)}` as PrefixedHexString) +} + +void main().catch((err) => { + console.error(err) + process.exitCode = 1 +}) From cb41cc8982f28f4995136581c30fcb903d089bc5 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Fri, 7 Nov 2025 16:12:17 -0700 Subject: [PATCH 02/10] fix case description --- packages/evm/examples/clz.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/examples/clz.ts b/packages/evm/examples/clz.ts index ddd5b82a70..6cc02b5186 100644 --- a/packages/evm/examples/clz.ts +++ b/packages/evm/examples/clz.ts @@ -45,7 +45,7 @@ const main = async () => { // Case 1: x == 0x00..00 -> expect 256 await runCase(evm, `0x${'00'.repeat(32)}` as PrefixedHexString) - // Case 2: x == 0x01..00 -> MSB at bit 255 -> expect 255 + // Case 2: x == 0x0..01 -> MSB at bit 0 -> expect 255 await runCase(evm, `0x${'00'.repeat(31)}01` as PrefixedHexString) // Case 3: x == 0x40..00 -> MSB at bit 254 -> expect 1 From 6fb25ac0927cdb9a0f4049fb861084811eac2ae7 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Sat, 8 Nov 2025 15:44:54 -0700 Subject: [PATCH 03/10] Add context to example --- packages/evm/examples/clz.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/evm/examples/clz.ts b/packages/evm/examples/clz.ts index 6cc02b5186..ad152acaef 100644 --- a/packages/evm/examples/clz.ts +++ b/packages/evm/examples/clz.ts @@ -2,6 +2,15 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { type EVM, createEVM } from '@ethereumjs/evm' import { type PrefixedHexString, hexToBytes } from '@ethereumjs/util' +// CLZ (Count Leading Zeros) opcode (0x1e) +// Demonstrates the CLZ opcode introduced in https://eips.ethereum.org/EIPS/eip-7939 +// +// The CLZ opcode returns the number of zero bits before the most significant 1-bit in a 256-bit value. +// It enables efficient computation of bit length, log₂, and prefix comparisons directly in the EVM. +// +// Doing this in Solidity/Yul requires a loop or binary search using shifts and comparisons, which costs hundreds of gas and bloats bytecode. +// By replacing multi-step shift and branch logic with a single instruction, it reduces gas cost, bytecode size, and zk-proof complexity. + const common = new Common({ chain: Mainnet, hardfork: Hardfork.Osaka, From be35679fbcdaf65af0390c122d83bbcd76a827fc Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Sat, 8 Nov 2025 15:45:14 -0700 Subject: [PATCH 04/10] improve log output --- packages/evm/examples/clz.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/evm/examples/clz.ts b/packages/evm/examples/clz.ts index ad152acaef..ddcd76ca0c 100644 --- a/packages/evm/examples/clz.ts +++ b/packages/evm/examples/clz.ts @@ -45,7 +45,9 @@ const runCase = async (evm: EVM, x: PrefixedHexString) => { const [top] = stack.peek(1) const hexValue = `0x${top.toString(16)}` - console.log(`clz=${top} (hex=${hexValue}) for x=${x}`) + const gas = res.executionGasUsed + console.log(`input=${x}`) + console.log(`output=${hexValue} -- clz=${top} -- gas=${gas}`) } const main = async () => { From d97143295ba7ec925a6caa0e9b2e470491e4a1c6 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 18 Nov 2025 19:47:13 -0700 Subject: [PATCH 05/10] rename file and move to opcodes subfolder --- packages/evm/examples/{clz.ts => opcodes/countLeadingZeros.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/evm/examples/{clz.ts => opcodes/countLeadingZeros.ts} (100%) diff --git a/packages/evm/examples/clz.ts b/packages/evm/examples/opcodes/countLeadingZeros.ts similarity index 100% rename from packages/evm/examples/clz.ts rename to packages/evm/examples/opcodes/countLeadingZeros.ts From e2a793c1262ebcb416dace72e910e7d726a61041 Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 18 Nov 2025 19:54:40 -0700 Subject: [PATCH 06/10] update examples runner script --- packages/evm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/package.json b/packages/evm/package.json index e7d6ecc8bd..26a6ed281c 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -36,7 +36,7 @@ "coverage": "DEBUG=ethjs npx vitest run -c ../../config/vitest.config.coverage.mts", "coverage:istanbul": "DEBUG=ethjs npx vitest run -c ../../config/vitest.config.coverage.istanbul.mts", "docs:build": "typedoc --options typedoc.mjs", - "examples": "tsx ../../scripts/examples-runner.ts -- evm && tsx ../../scripts/examples-runner.ts -- evm precompiles", + "examples": "tsx ../../scripts/examples-runner.ts -- evm && tsx ../../scripts/examples-runner.ts -- evm precompiles && tsx ../../scripts/examples-runner.ts -- evm opcodes", "examples:build": "npx embedme README.md", "formatTest": "node ./scripts/formatTest", "lint": "npm run biome && eslint --config ./eslint.config.mjs .", From d6fd7f3db55253ea46555b0492c6d64b6a3cae0a Mon Sep 17 00:00:00 2001 From: ScottyPoi Date: Tue, 18 Nov 2025 19:55:05 -0700 Subject: [PATCH 07/10] make example logging style match other examples --- packages/evm/examples/opcodes/countLeadingZeros.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/evm/examples/opcodes/countLeadingZeros.ts b/packages/evm/examples/opcodes/countLeadingZeros.ts index ddcd76ca0c..9d41057711 100644 --- a/packages/evm/examples/opcodes/countLeadingZeros.ts +++ b/packages/evm/examples/opcodes/countLeadingZeros.ts @@ -46,8 +46,10 @@ const runCase = async (evm: EVM, x: PrefixedHexString) => { const [top] = stack.peek(1) const hexValue = `0x${top.toString(16)}` const gas = res.executionGasUsed + console.log('--------------------------------') console.log(`input=${x}`) - console.log(`output=${hexValue} -- clz=${top} -- gas=${gas}`) + console.log(`output=${hexValue} (leading zeros=${top})`) + console.log(`Gas used: ${gas}`) } const main = async () => { @@ -64,6 +66,7 @@ const main = async () => { // Case 4: x == 0x80..00 -> MSB at bit 255 -> expect 0 await runCase(evm, `0x80${'00'.repeat(31)}` as PrefixedHexString) + console.log('--------------------------------') } void main().catch((err) => { From 02ecb929a2d6292e2d8f3ac83c6cd430ebe44947 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 19 Nov 2025 13:50:19 +0100 Subject: [PATCH 08/10] Some systematic for renaming to stay a bit more future proof --- .../{countLeadingZeros.ts => 1e-CLZ-count-leading-zeros.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/evm/examples/opcodes/{countLeadingZeros.ts => 1e-CLZ-count-leading-zeros.ts} (100%) diff --git a/packages/evm/examples/opcodes/countLeadingZeros.ts b/packages/evm/examples/opcodes/1e-CLZ-count-leading-zeros.ts similarity index 100% rename from packages/evm/examples/opcodes/countLeadingZeros.ts rename to packages/evm/examples/opcodes/1e-CLZ-count-leading-zeros.ts From 9d4f4ef74753307be74be5d0ac609acd99d2698b Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 19 Nov 2025 13:51:53 +0100 Subject: [PATCH 09/10] Another small example name adjustment --- ...CLZ-count-leading-zeros.ts => 0x1e-CLZ-count-leading-zeros.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/evm/examples/opcodes/{1e-CLZ-count-leading-zeros.ts => 0x1e-CLZ-count-leading-zeros.ts} (100%) diff --git a/packages/evm/examples/opcodes/1e-CLZ-count-leading-zeros.ts b/packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts similarity index 100% rename from packages/evm/examples/opcodes/1e-CLZ-count-leading-zeros.ts rename to packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts From 58f5aeefb3af0274a19474a7c4116dae9b371d5c Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Wed, 19 Nov 2025 13:54:31 +0100 Subject: [PATCH 10/10] Some small optimizations --- .../examples/opcodes/0x1e-CLZ-count-leading-zeros.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts b/packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts index 9d41057711..31f2d2ca3d 100644 --- a/packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts +++ b/packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts @@ -8,13 +8,13 @@ import { type PrefixedHexString, hexToBytes } from '@ethereumjs/util' // The CLZ opcode returns the number of zero bits before the most significant 1-bit in a 256-bit value. // It enables efficient computation of bit length, log₂, and prefix comparisons directly in the EVM. // -// Doing this in Solidity/Yul requires a loop or binary search using shifts and comparisons, which costs hundreds of gas and bloats bytecode. -// By replacing multi-step shift and branch logic with a single instruction, it reduces gas cost, bytecode size, and zk-proof complexity. +// Doing this in Solidity/Yul requires a loop or binary search using shifts and comparisons, +// which costs hundreds of gas and bloats bytecode. By replacing multi-step shift and branch logic +// with a single instruction, it reduces gas cost, bytecode size, and zk-proof complexity. const common = new Common({ chain: Mainnet, hardfork: Hardfork.Osaka, - eips: [7939], }) const assembleCode = (x: Uint8Array) => { @@ -33,10 +33,7 @@ const assembleCode = (x: Uint8Array) => { const runCase = async (evm: EVM, x: PrefixedHexString) => { const code = assembleCode(hexToBytes(x)) - const res = await evm.runCode({ - code, - gasLimit: 1_000_000n, - }) + const res = await evm.runCode({ code }) const stack = res.runState?.stack if (!stack) {