From ec697ddbdb4d853603b5f7d03272ae54504b1c2d Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Tue, 10 Feb 2026 19:21:20 +0800 Subject: [PATCH] core/vm: implement EIP-7939: CLZ opcode #31989 (#2012) --- core/vm/eips.go | 18 ++++++++++++++++ core/vm/evm.go | 2 ++ core/vm/instructions_test.go | 40 ++++++++++++++++++++++++++++++++++++ core/vm/jump_table.go | 7 +++++++ core/vm/jump_table_export.go | 4 +--- core/vm/opcodes.go | 3 +++ 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index ea86f4f90f..e63cde393f 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -40,6 +40,7 @@ var activators = map[int]func(*JumpTable){ 1344: enable1344, 1153: enable1153, 7702: enable7702, + 7939: enable7939, } // EnableEIP enables the given EIP on the config. @@ -282,6 +283,13 @@ func opBlobBaseFee(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } +// opCLZ implements the CLZ opcode (count leading zero bits) +func opCLZ(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + x.SetUint64(256 - uint64(x.BitLen())) + return nil, nil +} + // enable4844 applies EIP-4844 (BLOBHASH opcode) func enable4844(jt *JumpTable) { jt[BLOBHASH] = &operation{ @@ -292,6 +300,16 @@ func enable4844(jt *JumpTable) { } } +// enable7939 enables EIP-7939 (CLZ opcode) +func enable7939(jt *JumpTable) { + jt[CLZ] = &operation{ + execute: opCLZ, + constantGas: GasFastStep, + 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/evm.go b/core/vm/evm.go index 08fc435034..75c0ab0008 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -149,6 +149,8 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, tradingStat evm.precompiles = activePrecompiledContracts(evm.chainRules) switch { + case evm.chainRules.IsOsaka: + evm.table = &osakaInstructionSet case evm.chainRules.IsPrague: evm.table = &pragueInstructionSet case evm.chainRules.IsCancun: diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 18bb333384..f262fd3367 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -907,3 +907,43 @@ func TestOpMCopy(t *testing.T) { } } } + +func TestOpCLZ(t *testing.T) { + evm := NewEVM(BlockContext{}, TxContext{}, nil, nil, params.TestChainConfig, Config{}) + + tests := []struct { + inputHex string + want uint64 // expected CLZ result + }{ + {"0x0", 256}, + {"0x1", 255}, + {"0x6ff", 245}, // 0x6ff = 0b11011111111 (11 bits), so 256-11 = 245 + {"0xffffffffff", 216}, // 40 bits, so 256-40 = 216 + {"0x4000000000000000000000000000000000000000000000000000000000000000", 1}, + {"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 1}, + {"0x8000000000000000000000000000000000000000000000000000000000000000", 0}, + {"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0}, + } + for _, tc := range tests { + // prepare a fresh stack and PC + stack := newstack() + pc := uint64(0) + + // parse input + val := new(uint256.Int) + if err := val.SetFromHex(tc.inputHex); err != nil { + t.Fatal("invalid hex uint256:", tc.inputHex) + } + + stack.push(val) + opCLZ(&pc, evm, &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/jump_table.go b/core/vm/jump_table.go index 662fb0355e..024f91739f 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -59,6 +59,7 @@ var ( eip1559InstructionSet = newEip1559InstructionSet() cancunInstructionSet = newCancunInstructionSet() pragueInstructionSet = newPragueInstructionSet() + osakaInstructionSet = newOsakaInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -82,6 +83,12 @@ func validate(jt JumpTable) JumpTable { return jt } +func newOsakaInstructionSet() JumpTable { + instructionSet := newPragueInstructionSet() + enable7939(&instructionSet) // EIP-7939 (CLZ opcode) + return validate(instructionSet) +} + func newPragueInstructionSet() JumpTable { instructionSet := newCancunInstructionSet() enable7702(&instructionSet) // EIP-7702 Setcode transaction type diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 246f70480b..f6caa65b3b 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -17,8 +17,6 @@ package vm import ( - "errors" - "github.com/XinFinOrg/XDPoSChain/params" ) @@ -27,7 +25,7 @@ import ( func LookupInstructionSet(rules params.Rules) (JumpTable, error) { switch { 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 66b429079d..d094b4e05c 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -60,6 +60,7 @@ const ( SHL OpCode = 0x1b SHR OpCode = 0x1c SAR OpCode = 0x1d + CLZ OpCode = 0x1e ) // 0x20 range - crypto. @@ -252,6 +253,7 @@ var opCodeToString = [256]string{ SHL: "SHL", SHR: "SHR", SAR: "SAR", + CLZ: "CLZ", ADDMOD: "ADDMOD", MULMOD: "MULMOD", @@ -431,6 +433,7 @@ var stringToOp = map[string]OpCode{ "SHL": SHL, "SHR": SHR, "SAR": SAR, + "CLZ": CLZ, "ADDMOD": ADDMOD, "MULMOD": MULMOD, "KECCAK256": KECCAK256,