diff --git a/core/vm/eips.go b/core/vm/eips.go index 79fd24d13e..774fadcfe9 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -293,6 +293,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{ @@ -303,6 +310,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/jump_table.go b/core/vm/jump_table.go index 17ac738c98..d09bf717c3 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -91,6 +91,12 @@ func newVerkleInstructionSet() JumpTable { return validate(instructionSet) } +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 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,