diff --git a/core/vm/errors.go b/core/vm/errors.go index e33c9fcb85..b6235d44a6 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -76,10 +76,16 @@ func (e ErrStackOverflow) Unwrap() error { // ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. 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 // but we do not want to depend on rpc package here so we redefine it. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 74400732ac..c3d1633c78 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -996,7 +996,8 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // This range is excluded to preserve compatibility with existing opcodes. if x > 90 && x < 128 { - return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + operand := x + return nil, &ErrInvalidOpCode{opcode: DUPN, operand: &operand} } 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. if x > 90 && x < 128 { - return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + operand := x + return nil, &ErrInvalidOpCode{opcode: SWAPN, operand: &operand} } 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 // and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–81, 128–255). if x > 81 && x < 128 { - return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + operand := x + return nil, &ErrInvalidOpCode{opcode: EXCHANGE, operand: &operand} } n, m := decodePair(x) need := max(n, m) + 1 diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 1f69eea3da..5055ccb16d 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1014,11 +1014,12 @@ func TestEIP8024_Execution(t *testing.T) { evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) tests := []struct { - name string - codeHex string - wantErr error - wantOpcode OpCode - wantVals []uint64 + name string + codeHex string + wantErr error + wantOpcode OpCode + wantOperand *byte + wantVals []uint64 }{ { name: "DUPN", @@ -1063,10 +1064,18 @@ func TestEIP8024_Execution(t *testing.T) { }, }, { - name: "INVALID_SWAPN_LOW", - codeHex: "e75b", - wantErr: &ErrInvalidOpCode{}, - wantOpcode: SWAPN, + name: "INVALID_DUPN_LOW", + codeHex: "e65b", + wantErr: &ErrInvalidOpCode{}, + wantOpcode: DUPN, + wantOperand: ptrToByte(0x5b), + }, + { + name: "INVALID_SWAPN_LOW", + codeHex: "e75b", + wantErr: &ErrInvalidOpCode{}, + wantOpcode: SWAPN, + wantOperand: ptrToByte(0x5b), }, { name: "JUMP_OVER_INVALID_DUPN", @@ -1079,10 +1088,11 @@ func TestEIP8024_Execution(t *testing.T) { wantVals: []uint64{1, 0, 0}, }, { - name: "INVALID_EXCHANGE", - codeHex: "e852", - wantErr: &ErrInvalidOpCode{}, - wantOpcode: EXCHANGE, + name: "INVALID_EXCHANGE", + codeHex: "e852", + wantErr: &ErrInvalidOpCode{}, + wantOpcode: EXCHANGE, + wantOperand: ptrToByte(0x52), }, { name: "UNDERFLOW_DUPN", @@ -1150,10 +1160,21 @@ func TestEIP8024_Execution(t *testing.T) { // Fail if we don't get the error we expect. switch tc.wantErr.(type) { case *ErrInvalidOpCode: - var want *ErrInvalidOpCode - if !errors.As(err, &want) { + var got *ErrInvalidOpCode + if !errors.As(err, &got) { 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: var want *ErrStackUnderflow if !errors.As(err, &want) { @@ -1183,3 +1204,8 @@ func TestEIP8024_Execution(t *testing.T) { }) } } + +func ptrToByte(v byte) *byte { + b := v + return &b +}