EIP7939: Count leading zeros (CLZ) opcode (#1246)

* Cherry pick eip7939

* sync latest
This commit is contained in:
georgehao 2025-10-21 15:48:48 +08:00 committed by GitHub
parent 9aa8b3f38f
commit 9668c2c322
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 82 additions and 2 deletions

View file

@ -39,6 +39,7 @@ var activators = map[int]func(*JumpTable){
1344: enable1344,
1153: enable1153,
7702: enable7702,
7939: enable7939,
}
// EnableEIP enables the given EIP on the config.
@ -280,3 +281,20 @@ func enable7702(jt *JumpTable) {
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
}
// opCLZ implements the CLZ opcode (count leading zero bits)
func opCLZ(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x := scope.Stack.peek()
x.SetUint64(256 - uint64(x.BitLen()))
return nil, nil
}
// 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),
}
}

View file

@ -848,3 +848,43 @@ func TestOpMCopy(t *testing.T) {
}
}
}
func TestOpCLZ(t *testing.T) {
evm := NewEVM(BlockContext{}, TxContext{}, 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.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

@ -74,6 +74,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if cfg.JumpTable[STOP] == nil {
var jt JumpTable
switch {
case evm.chainRules.IsGalileo:
jt = galileoInstructionSet
case evm.chainRules.IsFeynman:
jt = feynmanInstructionSet
case evm.chainRules.IsEuclidV2:

View file

@ -63,11 +63,21 @@ var (
darwinInstructionSet = newDarwinInstructionSet()
euclidV2InstructionSet = newEuclidV2InstructionSet()
feynmanInstructionSet = newFeynmanInstructionSet()
galileoInstructionSet = newGalileoInstructionSet()
)
// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation
// newGalileoInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, darwin, euclidV2,
// feynman and galileo instructions.
func newGalileoInstructionSet() JumpTable {
instructionSet := newFeynmanInstructionSet()
enable7939(&instructionSet) // EIP-7939 (CLZ opcode)
return instructionSet
}
// newFeynmanInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, darwin, euclidV2,
// and feynman instructions.

View file

@ -69,6 +69,7 @@ const (
SHL OpCode = 0x1b
SHR OpCode = 0x1c
SAR OpCode = 0x1d
CLZ OpCode = 0x1e
SHA3 OpCode = 0x20
)
@ -255,6 +256,7 @@ var opCodeToString = map[OpCode]string{
SHL: "SHL",
SHR: "SHR",
SAR: "SAR",
CLZ: "CLZ",
ADDMOD: "ADDMOD",
MULMOD: "MULMOD",
@ -430,6 +432,7 @@ var stringToOp = map[string]OpCode{
"SHL": SHL,
"SHR": SHR,
"SAR": SAR,
"CLZ": CLZ,
"ADDMOD": ADDMOD,
"MULMOD": MULMOD,
"SHA3": SHA3,

View file

@ -670,6 +670,7 @@ type ChainConfig struct {
EuclidTime *uint64 `json:"euclidTime,omitempty"` // Euclid switch time (nil = no fork, 0 = already on euclid)
EuclidV2Time *uint64 `json:"euclidv2Time,omitempty"` // EuclidV2 switch time (nil = no fork, 0 = already on euclidv2)
FeynmanTime *uint64 `json:"feynmanTime,omitempty"` // Feynman switch time (nil = no fork, 0 = already on feynman)
GalileoTime *uint64 `json:"galileoTime,omitempty"` // Galileo switch time (nil = no fork, 0 = already on galileo)
// TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade.
@ -1014,6 +1015,10 @@ func (c *ChainConfig) IsFeynman(now uint64) bool {
return isForkedTime(now, c.FeynmanTime)
}
func (c *ChainConfig) IsGalileo(now uint64) bool {
return isForkedTime(now, c.GalileoTime)
}
// IsFeynmanTransitionBlock returns whether the given block timestamp corresponds to the first Feynman block.
func (c *ChainConfig) IsFeynmanTransitionBlock(blockTimestamp uint64, parentTimestamp uint64) bool {
return isForkedTime(blockTimestamp, c.FeynmanTime) && !isForkedTime(parentTimestamp, c.FeynmanTime)
@ -1247,7 +1252,7 @@ type Rules struct {
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon, IsArchimedes, IsShanghai bool
IsBernoulli, IsCurie, IsDarwin, IsEuclid, IsEuclidV2 bool
IsFeynman bool
IsFeynman, IsGalileo bool
}
// Rules ensures c's ChainID is not nil.
@ -1276,5 +1281,6 @@ func (c *ChainConfig) Rules(num *big.Int, time uint64) Rules {
IsEuclid: c.IsEuclid(time),
IsEuclidV2: c.IsEuclidV2(time),
IsFeynman: c.IsFeynman(time),
IsGalileo: c.IsGalileo(time),
}
}

View file

@ -5,7 +5,7 @@ go 1.22
replace github.com/scroll-tech/go-ethereum => ../../..
require (
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
github.com/scroll-tech/da-codec v0.1.3-0.20250825071838-cddc263e5ef6
github.com/scroll-tech/go-ethereum v1.10.14-0.20250625112225-a67863c65587
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0

View file

@ -173,6 +173,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 h1:Dhd58LE1D+dnoxpgLVeQBMF9uweL/fhQfZHWtWSiOlE=
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY=
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
github.com/scroll-tech/da-codec v0.1.3-0.20250825071838-cddc263e5ef6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=