core/vm: implement eip-7939 CLZ instruction

Co-authored-by: Giulio <giulio.rebuffo@gmail.com>
Co-authored-by: spencer-tb <spencer@spencertaylorbrown.uk>
This commit is contained in:
lightclient 2025-06-23 20:12:07 +02:00
parent 4a4dc08ab1
commit 18e79fdd7e
No known key found for this signature in database
GPG key ID: 657913021EF45A6A
6 changed files with 68 additions and 1 deletions

View file

@ -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{

View file

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

View file

@ -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

View file

@ -95,6 +95,7 @@ func newVerkleInstructionSet() JumpTable {
func newOsakaInstructionSet() JumpTable {
instructionSet := newPragueInstructionSet()
enable7907(&instructionSet)
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
return validate(instructionSet)
}

View file

@ -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:

View file

@ -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,