From 18e79fdd7e36570802fdb7c3229d35eb6dc01415 Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 23 Jun 2025 20:12:07 +0200 Subject: [PATCH] core/vm: implement eip-7939 CLZ instruction Co-authored-by: Giulio Co-authored-by: spencer-tb --- core/vm/eips.go | 17 ++++++++++++++ core/vm/instructions_test.go | 44 ++++++++++++++++++++++++++++++++++++ core/vm/interpreter.go | 2 ++ core/vm/jump_table.go | 1 + core/vm/jump_table_export.go | 2 +- core/vm/opcodes.go | 3 +++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 76c5d47211..5f0617377e 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -42,6 +42,7 @@ var activators = map[int]func(*JumpTable){ 4762: enable4762, 7702: enable7702, 7907: enable7907, + 7939: enable7939, } // EnableEIP enables the given EIP on the config. @@ -294,6 +295,13 @@ func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) return nil, nil } +func opCLZ(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.pop() + // count leading zero bits in x + scope.Stack.push(new(uint256.Int).SetUint64(256 - uint64(x.BitLen()))) + return nil, nil +} + // enable4844 applies EIP-4844 (BLOBHASH opcode) func enable4844(jt *JumpTable) { jt[BLOBHASH] = &operation{ @@ -304,6 +312,15 @@ func enable4844(jt *JumpTable) { } } +func enable7939(jt *JumpTable) { + jt[CLZ] = &operation{ + execute: opCLZ, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } +} + // enable7516 applies EIP-7516 (BLOBBASEFEE opcode) func enable7516(jt *JumpTable) { jt[BLOBBASEFEE] = &operation{ diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0902d17c54..9b7e7e4d46 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -972,3 +972,47 @@ func TestPush(t *testing.T) { } } } + +func TestOpCLZ(t *testing.T) { + // set up once + evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + + tests := []struct { + name string + inputHex string // hexadecimal input for clarity + want uint64 // expected CLZ result + }{ + {"zero", "0x0", 256}, + {"one", "0x1", 255}, + {"all-ones (256 bits)", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0}, + {"low-10-bytes ones", "0xffffffffff", 216}, // 10 bytes = 80 bits, so 256-80=176? Actually input is 0xffffffffff = 40 bits so 256-40=216 + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + // prepare a fresh stack and PC + stack := newstack() + pc := uint64(0) + + // parse input + val := new(uint256.Int) + if _, err := fmt.Sscan(tc.inputHex, val); err != nil { + // fallback: try hex + val.SetFromHex(tc.inputHex) + } + + stack.push(val) + opCLZ(&pc, evm.interpreter, &ScopeContext{Stack: stack}) + + if gotLen := stack.len(); gotLen != 1 { + t.Fatalf("stack length = %d; want 1", gotLen) + } + result := stack.pop() + + if got := result.Uint64(); got != tc.want { + t.Fatalf("clz(%q) = %d; want %d", tc.inputHex, got, tc.want) + } + }) + } +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index bd499750eb..9b64baa610 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -106,6 +106,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // If jump table was not initialised we set the default one. var table *JumpTable switch { + case evm.chainRules.IsOsaka: + table = &osakaInstructionSet case evm.chainRules.IsVerkle: // TODO replace with proper instruction set when fork is specified table = &verkleInstructionSet diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index bb2e40d2b3..2f4dab0b74 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -95,6 +95,7 @@ func newVerkleInstructionSet() JumpTable { func newOsakaInstructionSet() JumpTable { instructionSet := newPragueInstructionSet() enable7907(&instructionSet) + enable7939(&instructionSet) // EIP-7939 (CLZ opcode) return validate(instructionSet) } diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index b8fa6049bb..89a2ebf6f4 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -29,7 +29,7 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { case rules.IsVerkle: return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") case rules.IsOsaka: - return newPragueInstructionSet(), errors.New("osaka-fork not defined yet") + return newOsakaInstructionSet(), nil case rules.IsPrague: return newPragueInstructionSet(), nil case rules.IsCancun: diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 0820b20fb1..9a32126a80 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -62,6 +62,7 @@ const ( SHL OpCode = 0x1b SHR OpCode = 0x1c SAR OpCode = 0x1d + CLZ OpCode = 0x1e ) // 0x20 range - crypto. @@ -282,6 +283,7 @@ var opCodeToString = [256]string{ SHL: "SHL", SHR: "SHR", SAR: "SAR", + CLZ: "CLZ", ADDMOD: "ADDMOD", MULMOD: "MULMOD", @@ -484,6 +486,7 @@ var stringToOp = map[string]OpCode{ "SHL": SHL, "SHR": SHR, "SAR": SAR, + "CLZ": CLZ, "ADDMOD": ADDMOD, "MULMOD": MULMOD, "KECCAK256": KECCAK256,