Skip to content
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
72 changes: 72 additions & 0 deletions packages/evm/examples/opcodes/0x1e-CLZ-count-leading-zeros.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the name of this file more expressive, so that people can spot what the example is about?
(clz might be in the head of people now, but will be forgotten soon).

So that would be clz-opcode.ts or so.
(simple solution)

I you want to go a little extra mile I would also be a fan of the a bit extended version, which would be to keep the name but do a subfolder opcodes (like precompiles).

I prepared for that to be possible (in the sense that the examples still run), this would need a slight addition to the package.json examples script (should be obvious).

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,
})

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 })

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)}`
const gas = res.executionGasUsed
console.log('--------------------------------')
console.log(`input=${x}`)
console.log(`output=${hexValue} (leading zeros=${top})`)
console.log(`Gas used: ${gas}`)
}

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 == 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
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)
console.log('--------------------------------')
}

void main().catch((err) => {
console.error(err)
process.exitCode = 1
})
2 changes: 1 addition & 1 deletion packages/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 .",
Expand Down
Loading