core/vm: include operand in error message (#34635)

Return ErrInvalidOpCode with the executing opcode and offending
immediate for forbidden DUPN, SWAPN, and EXCHANGE operands. Extend
TestEIP8024_Execution to assert both opcode and operand for all
invalid-immediate paths.
This commit is contained in:
Daniel Liu 2026-04-13 20:13:33 +08:00 committed by GitHub
parent 7d463aedd3
commit 5b7511eeed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 55 additions and 20 deletions

View file

@ -76,10 +76,16 @@ func (e ErrStackOverflow) Unwrap() error {
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. // ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
type ErrInvalidOpCode struct { type ErrInvalidOpCode struct {
opcode OpCode opcode OpCode
operand *byte
} }
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } func (e *ErrInvalidOpCode) Error() string {
if e.operand != nil {
return fmt.Sprintf("invalid opcode: %s (operand: 0x%02x)", e.opcode, *e.operand)
}
return fmt.Sprintf("invalid opcode: %s", e.opcode)
}
// rpcError is the same interface as the one defined in rpc/errors.go // rpcError is the same interface as the one defined in rpc/errors.go
// but we do not want to depend on rpc package here so we redefine it. // but we do not want to depend on rpc package here so we redefine it.

View file

@ -996,7 +996,8 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// This range is excluded to preserve compatibility with existing opcodes. // This range is excluded to preserve compatibility with existing opcodes.
if x > 90 && x < 128 { if x > 90 && x < 128 {
return nil, &ErrInvalidOpCode{opcode: OpCode(x)} operand := x
return nil, &ErrInvalidOpCode{opcode: DUPN, operand: &operand}
} }
n := decodeSingle(x) n := decodeSingle(x)
@ -1023,7 +1024,8 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// This range is excluded to preserve compatibility with existing opcodes. // This range is excluded to preserve compatibility with existing opcodes.
if x > 90 && x < 128 { if x > 90 && x < 128 {
return nil, &ErrInvalidOpCode{opcode: OpCode(x)} operand := x
return nil, &ErrInvalidOpCode{opcode: SWAPN, operand: &operand}
} }
n := decodeSingle(x) n := decodeSingle(x)
@ -1053,7 +1055,8 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// This range is excluded both to preserve compatibility with existing opcodes // This range is excluded both to preserve compatibility with existing opcodes
// and to keep decode_pairs 16-aligned arithmetic mapping valid (081, 128255). // and to keep decode_pairs 16-aligned arithmetic mapping valid (081, 128255).
if x > 81 && x < 128 { if x > 81 && x < 128 {
return nil, &ErrInvalidOpCode{opcode: OpCode(x)} operand := x
return nil, &ErrInvalidOpCode{opcode: EXCHANGE, operand: &operand}
} }
n, m := decodePair(x) n, m := decodePair(x)
need := max(n, m) + 1 need := max(n, m) + 1

View file

@ -1014,11 +1014,12 @@ func TestEIP8024_Execution(t *testing.T) {
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
tests := []struct { tests := []struct {
name string name string
codeHex string codeHex string
wantErr error wantErr error
wantOpcode OpCode wantOpcode OpCode
wantVals []uint64 wantOperand *byte
wantVals []uint64
}{ }{
{ {
name: "DUPN", name: "DUPN",
@ -1063,10 +1064,18 @@ func TestEIP8024_Execution(t *testing.T) {
}, },
}, },
{ {
name: "INVALID_SWAPN_LOW", name: "INVALID_DUPN_LOW",
codeHex: "e75b", codeHex: "e65b",
wantErr: &ErrInvalidOpCode{}, wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN, wantOpcode: DUPN,
wantOperand: ptrToByte(0x5b),
},
{
name: "INVALID_SWAPN_LOW",
codeHex: "e75b",
wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
wantOperand: ptrToByte(0x5b),
}, },
{ {
name: "JUMP_OVER_INVALID_DUPN", name: "JUMP_OVER_INVALID_DUPN",
@ -1079,10 +1088,11 @@ func TestEIP8024_Execution(t *testing.T) {
wantVals: []uint64{1, 0, 0}, wantVals: []uint64{1, 0, 0},
}, },
{ {
name: "INVALID_EXCHANGE", name: "INVALID_EXCHANGE",
codeHex: "e852", codeHex: "e852",
wantErr: &ErrInvalidOpCode{}, wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE, wantOpcode: EXCHANGE,
wantOperand: ptrToByte(0x52),
}, },
{ {
name: "UNDERFLOW_DUPN", name: "UNDERFLOW_DUPN",
@ -1150,10 +1160,21 @@ func TestEIP8024_Execution(t *testing.T) {
// Fail if we don't get the error we expect. // Fail if we don't get the error we expect.
switch tc.wantErr.(type) { switch tc.wantErr.(type) {
case *ErrInvalidOpCode: case *ErrInvalidOpCode:
var want *ErrInvalidOpCode var got *ErrInvalidOpCode
if !errors.As(err, &want) { if !errors.As(err, &got) {
t.Fatalf("expected ErrInvalidOpCode, got %v", err) t.Fatalf("expected ErrInvalidOpCode, got %v", err)
} }
if got.opcode != tc.wantOpcode {
t.Fatalf("ErrInvalidOpCode.opcode=%s; want %s", got.opcode, tc.wantOpcode)
}
if tc.wantOperand != nil {
if got.operand == nil {
t.Fatalf("ErrInvalidOpCode.operand=nil; want 0x%02x", *tc.wantOperand)
}
if *got.operand != *tc.wantOperand {
t.Fatalf("ErrInvalidOpCode.operand=0x%02x; want 0x%02x", *got.operand, *tc.wantOperand)
}
}
case *ErrStackUnderflow: case *ErrStackUnderflow:
var want *ErrStackUnderflow var want *ErrStackUnderflow
if !errors.As(err, &want) { if !errors.As(err, &want) {
@ -1183,3 +1204,8 @@ func TestEIP8024_Execution(t *testing.T) {
}) })
} }
} }
func ptrToByte(v byte) *byte {
b := v
return &b
}