Skip to content

Commit dab9c51

Browse files
joshklopclaude
andcommitted
feat(op-acceptance-tests): add EIP-7939 CLZ opcode acceptance test
Add TestEIP7939CLZ to verify the CLZ opcode (0x1e) is available after the Karst fork and unavailable before it. The test uses eth_call with init code that computes CLZ(1) = 255, asserting an invalid opcode error pre-fork and the correct result post-fork. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d9c1d39 commit dab9c51

File tree

1 file changed

+43
-0
lines changed

1 file changed

+43
-0
lines changed

op-acceptance-tests/tests/osaka_on_l2_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
1111
"github.com/ethereum/go-ethereum"
1212
"github.com/ethereum/go-ethereum/common"
13+
"github.com/ethereum/go-ethereum/core/vm"
1314
"github.com/ethereum/go-ethereum/rpc"
1415
)
1516

@@ -72,3 +73,45 @@ func TestEIP7823UpperBoundModExp(gt *testing.T) {
7273
t.Require().NoError(err)
7374
t.Require().Equal([]byte{3}, result, "2^3 mod 5 should equal 3")
7475
}
76+
77+
func TestEIP7939CLZ(gt *testing.T) {
78+
t := devtest.ParallelT(gt)
79+
sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth")
80+
81+
karstOffset := uint64(3)
82+
sys := presets.NewMinimal(t, presets.WithDeployerOptions(sysgo.WithKarstAtOffset(&karstOffset)))
83+
84+
activationBlock := sys.L2Chain.AwaitActivation(t, forks.Karst)
85+
t.Require().Greater(activationBlock.Number, uint64(0), "karst must not activate at genesis")
86+
preForkBlockNum := activationBlock.Number - 1
87+
postForkBlockNum := activationBlock.Number + 1
88+
sys.L2EL.WaitForBlockNumber(postForkBlockNum)
89+
90+
l2Client := sys.L2EL.EthClient()
91+
92+
// EVM init code that computes CLZ(1) and returns the 32-byte result.
93+
// CLZ(1) = 255 because 1 has 255 leading zero bits in a uint256.
94+
clzCode := []byte{
95+
byte(vm.PUSH1), 1, // stack: [1]
96+
byte(vm.CLZ), // stack: [255] (1 has 255 leading zeros)
97+
byte(vm.PUSH1), 0, // stack: [0, 255]
98+
byte(vm.MSTORE), // mem[0:32] = 255
99+
byte(vm.PUSH1), 32, // stack: [32]
100+
byte(vm.PUSH1), 0, // stack: [0, 32]
101+
byte(vm.RETURN), // return mem[0:32]
102+
}
103+
104+
// Pre-fork: CLZ opcode (0x1e) is not yet valid, so execution should fail.
105+
_, err := l2Client.Call(t.Ctx(), ethereum.CallMsg{
106+
Data: clzCode,
107+
}, rpc.BlockNumber(preForkBlockNum))
108+
t.Require().Error(err, "pre-fork: CLZ opcode should not be available")
109+
110+
// Post-fork: CLZ opcode is valid, execution should succeed.
111+
result, err := l2Client.Call(t.Ctx(), ethereum.CallMsg{
112+
Data: clzCode,
113+
}, rpc.BlockNumber(postForkBlockNum))
114+
t.Require().NoError(err, "post-fork: CLZ opcode should be available")
115+
expected := common.LeftPadBytes([]byte{0xff}, 32) // 255 as uint256
116+
t.Require().Equal(expected, result, "CLZ(1) should equal 255")
117+
}

0 commit comments

Comments
 (0)