Skip to content

Commit 70b1724

Browse files
Giulio2002spencer-tbfjl
authored andcommitted
core/vm: implement EIP-7939 - CLZ opcode (ethereum#31989)
https://eips.ethereum.org/EIPS/eip-7939 --------- Co-authored-by: spencer-tb <spencer@spencertaylorbrown.uk> Co-authored-by: Felix Lange <fjl@twurst.com>
1 parent b99c4b9 commit 70b1724

File tree

6 files changed

+70
-1
lines changed

6 files changed

+70
-1
lines changed

core/vm/eips.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var activators = map[int]func(*JumpTable){
4141
1153: enable1153,
4242
4762: enable4762,
4343
7702: enable7702,
44+
7939: enable7939,
4445
}
4546

4647
// EnableEIP enables the given EIP on the config.
@@ -293,6 +294,13 @@ func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
293294
return nil, nil
294295
}
295296

297+
// opCLZ implements the CLZ opcode (count leading zero bytes)
298+
func opCLZ(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
299+
x := scope.Stack.peek()
300+
x.SetUint64(256 - uint64(x.BitLen()))
301+
return nil, nil
302+
}
303+
296304
// enable4844 applies EIP-4844 (BLOBHASH opcode)
297305
func enable4844(jt *JumpTable) {
298306
jt[BLOBHASH] = &operation{
@@ -303,6 +311,15 @@ func enable4844(jt *JumpTable) {
303311
}
304312
}
305313

314+
func enable7939(jt *JumpTable) {
315+
jt[CLZ] = &operation{
316+
execute: opCLZ,
317+
constantGas: GasFastestStep,
318+
minStack: minStack(1, 1),
319+
maxStack: maxStack(1, 1),
320+
}
321+
}
322+
306323
// enable7516 applies EIP-7516 (BLOBBASEFEE opcode)
307324
func enable7516(jt *JumpTable) {
308325
jt[BLOBBASEFEE] = &operation{

core/vm/instructions_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,3 +972,43 @@ func TestPush(t *testing.T) {
972972
}
973973
}
974974
}
975+
976+
func TestOpCLZ(t *testing.T) {
977+
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
978+
979+
tests := []struct {
980+
inputHex string
981+
want uint64 // expected CLZ result
982+
}{
983+
{"0x0", 256},
984+
{"0x1", 255},
985+
{"0x6ff", 245}, // 0x6ff = 0b11011111111 (11 bits), so 256-11 = 245
986+
{"0xffffffffff", 216}, // 40 bits, so 256-40 = 216
987+
{"0x4000000000000000000000000000000000000000000000000000000000000000", 1},
988+
{"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 1},
989+
{"0x8000000000000000000000000000000000000000000000000000000000000000", 0},
990+
{"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0},
991+
}
992+
for _, tc := range tests {
993+
// prepare a fresh stack and PC
994+
stack := newstack()
995+
pc := uint64(0)
996+
997+
// parse input
998+
val := new(uint256.Int)
999+
if err := val.SetFromHex(tc.inputHex); err != nil {
1000+
t.Fatal("invalid hex uint256:", tc.inputHex)
1001+
}
1002+
1003+
stack.push(val)
1004+
opCLZ(&pc, evm.interpreter, &ScopeContext{Stack: stack})
1005+
1006+
if gotLen := stack.len(); gotLen != 1 {
1007+
t.Fatalf("stack length = %d; want 1", gotLen)
1008+
}
1009+
result := stack.pop()
1010+
if got := result.Uint64(); got != tc.want {
1011+
t.Fatalf("clz(%q) = %d; want %d", tc.inputHex, got, tc.want)
1012+
}
1013+
}
1014+
}

core/vm/interpreter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
106106
// If jump table was not initialised we set the default one.
107107
var table *JumpTable
108108
switch {
109+
case evm.chainRules.IsOsaka:
110+
table = &osakaInstructionSet
109111
case evm.chainRules.IsVerkle:
110112
// TODO replace with proper instruction set when fork is specified
111113
table = &verkleInstructionSet

core/vm/jump_table.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var (
6262
cancunInstructionSet = newCancunInstructionSet()
6363
verkleInstructionSet = newVerkleInstructionSet()
6464
pragueInstructionSet = newPragueInstructionSet()
65+
osakaInstructionSet = newOsakaInstructionSet()
6566
)
6667

6768
// JumpTable contains the EVM opcodes supported at a given fork.
@@ -91,6 +92,12 @@ func newVerkleInstructionSet() JumpTable {
9192
return validate(instructionSet)
9293
}
9394

95+
func newOsakaInstructionSet() JumpTable {
96+
instructionSet := newPragueInstructionSet()
97+
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
98+
return validate(instructionSet)
99+
}
100+
94101
func newPragueInstructionSet() JumpTable {
95102
instructionSet := newCancunInstructionSet()
96103
enable7702(&instructionSet) // EIP-7702 Setcode transaction type

core/vm/jump_table_export.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
2929
case rules.IsVerkle:
3030
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
3131
case rules.IsOsaka:
32-
return newPragueInstructionSet(), errors.New("osaka-fork not defined yet")
32+
return newOsakaInstructionSet(), nil
3333
case rules.IsPrague:
3434
return newPragueInstructionSet(), nil
3535
case rules.IsCancun:

core/vm/opcodes.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const (
6262
SHL OpCode = 0x1b
6363
SHR OpCode = 0x1c
6464
SAR OpCode = 0x1d
65+
CLZ OpCode = 0x1e
6566
)
6667

6768
// 0x20 range - crypto.
@@ -282,6 +283,7 @@ var opCodeToString = [256]string{
282283
SHL: "SHL",
283284
SHR: "SHR",
284285
SAR: "SAR",
286+
CLZ: "CLZ",
285287
ADDMOD: "ADDMOD",
286288
MULMOD: "MULMOD",
287289

@@ -484,6 +486,7 @@ var stringToOp = map[string]OpCode{
484486
"SHL": SHL,
485487
"SHR": SHR,
486488
"SAR": SAR,
489+
"CLZ": CLZ,
487490
"ADDMOD": ADDMOD,
488491
"MULMOD": MULMOD,
489492
"KECCAK256": KECCAK256,

0 commit comments

Comments
 (0)