vm: implement ISTYPE opcode

Also make StackItemType public and reorder it according to NEO3.
This commit is contained in:
Evgenii Stratonikov 2020-04-24 13:46:46 +03:00
parent 48a41bd737
commit d3b9aef8e2
6 changed files with 142 additions and 22 deletions

View file

@ -101,7 +101,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
} }
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL: opcode.CALL, opcode.ISTYPE:
numtoread = 1 numtoread = 1
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
@ -186,6 +186,9 @@ func (c *Context) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Context to Integer") return nil, errors.New("can't convert Context to Integer")
} }
// Type implements StackItem interface.
func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") }
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (c *Context) Equals(s StackItem) bool { func (c *Context) Equals(s StackItem) bool {
return c == s return c == s

View file

@ -162,6 +162,9 @@ const (
REMOVE Opcode = 0xD2 REMOVE Opcode = 0xD2
CLEARITEMS Opcode = 0xD3 CLEARITEMS Opcode = 0xD3
// Types
ISTYPE Opcode = 0xD9
// Exceptions // Exceptions
THROW Opcode = 0xF0 THROW Opcode = 0xF0
THROWIFNOT Opcode = 0xF1 THROWIFNOT Opcode = 0xF1

View file

@ -7,17 +7,61 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
) )
type stackItemType byte // StackItemType represents type of the stack item.
type StackItemType byte
// This block defines all known stack item types.
const ( const (
byteArrayT stackItemType = 0x00 AnyT StackItemType = 0x00
booleanT stackItemType = 0x01 PointerT StackItemType = 0x10
integerT stackItemType = 0x02 BooleanT StackItemType = 0x20
arrayT stackItemType = 0x80 IntegerT StackItemType = 0x21
structT stackItemType = 0x81 ByteArrayT StackItemType = 0x28
mapT stackItemType = 0x82 BufferT StackItemType = 0x30
ArrayT StackItemType = 0x40
StructT StackItemType = 0x41
MapT StackItemType = 0x48
InteropT StackItemType = 0x60
) )
// String implements fmt.Stringer interface.
func (t StackItemType) String() string {
switch t {
case AnyT:
return "Any"
case PointerT:
return "Pointer"
case BooleanT:
return "Boolean"
case IntegerT:
return "Integer"
case ByteArrayT:
return "ByteArray"
case BufferT:
return "Buffer"
case ArrayT:
return "Array"
case StructT:
return "Struct"
case MapT:
return "Map"
case InteropT:
return "Interop"
default:
return "INVALID"
}
}
// IsValid checks if s is a well defined stack item type.
func (t StackItemType) IsValid() bool {
switch t {
case AnyT, PointerT, BooleanT, IntegerT, ByteArrayT, BufferT, ArrayT, StructT, MapT, InteropT:
return true
default:
return false
}
}
// SerializeItem encodes given StackItem into the byte slice. // SerializeItem encodes given StackItem into the byte slice.
func SerializeItem(item StackItem) ([]byte, error) { func SerializeItem(item StackItem) ([]byte, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
@ -43,13 +87,13 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
switch t := item.(type) { switch t := item.(type) {
case *ByteArrayItem: case *ByteArrayItem:
w.WriteBytes([]byte{byte(byteArrayT)}) w.WriteBytes([]byte{byte(ByteArrayT)})
w.WriteVarBytes(t.value) w.WriteVarBytes(t.value)
case *BoolItem: case *BoolItem:
w.WriteBytes([]byte{byte(booleanT)}) w.WriteBytes([]byte{byte(BooleanT)})
w.WriteBool(t.value) w.WriteBool(t.value)
case *BigIntegerItem: case *BigIntegerItem:
w.WriteBytes([]byte{byte(integerT)}) w.WriteBytes([]byte{byte(IntegerT)})
w.WriteVarBytes(emit.IntToBytes(t.value)) w.WriteVarBytes(emit.IntToBytes(t.value))
case *InteropItem: case *InteropItem:
w.Err = errors.New("interop item can't be serialized") w.Err = errors.New("interop item can't be serialized")
@ -58,9 +102,9 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
_, isArray := t.(*ArrayItem) _, isArray := t.(*ArrayItem)
if isArray { if isArray {
w.WriteBytes([]byte{byte(arrayT)}) w.WriteBytes([]byte{byte(ArrayT)})
} else { } else {
w.WriteBytes([]byte{byte(structT)}) w.WriteBytes([]byte{byte(StructT)})
} }
arr := t.Value().([]StackItem) arr := t.Value().([]StackItem)
@ -71,7 +115,7 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
case *MapItem: case *MapItem:
seen[item] = true seen[item] = true
w.WriteBytes([]byte{byte(mapT)}) w.WriteBytes([]byte{byte(MapT)})
w.WriteVarUint(uint64(len(t.value))) w.WriteVarUint(uint64(len(t.value)))
for i := range t.value { for i := range t.value {
serializeItemTo(t.value[i].Key, w, seen) serializeItemTo(t.value[i].Key, w, seen)
@ -100,31 +144,31 @@ func DecodeBinaryStackItem(r *io.BinReader) StackItem {
return nil return nil
} }
switch stackItemType(t) { switch StackItemType(t) {
case byteArrayT: case ByteArrayT:
data := r.ReadVarBytes() data := r.ReadVarBytes()
return NewByteArrayItem(data) return NewByteArrayItem(data)
case booleanT: case BooleanT:
var b = r.ReadBool() var b = r.ReadBool()
return NewBoolItem(b) return NewBoolItem(b)
case integerT: case IntegerT:
data := r.ReadVarBytes() data := r.ReadVarBytes()
num := emit.BytesToInt(data) num := emit.BytesToInt(data)
return &BigIntegerItem{ return &BigIntegerItem{
value: num, value: num,
} }
case arrayT, structT: case ArrayT, StructT:
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
arr := make([]StackItem, size) arr := make([]StackItem, size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
arr[i] = DecodeBinaryStackItem(r) arr[i] = DecodeBinaryStackItem(r)
} }
if stackItemType(t) == arrayT { if StackItemType(t) == ArrayT {
return &ArrayItem{value: arr} return &ArrayItem{value: arr}
} }
return &StructItem{value: arr} return &StructItem{value: arr}
case mapT: case MapT:
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
m := NewMapItem() m := NewMapItem()
for i := 0; i < size; i++ { for i := 0; i < size; i++ {

View file

@ -28,6 +28,8 @@ type StackItem interface {
Equals(s StackItem) bool Equals(s StackItem) bool
// ToContractParameter converts StackItem to smartcontract.Parameter // ToContractParameter converts StackItem to smartcontract.Parameter
ToContractParameter(map[StackItem]bool) smartcontract.Parameter ToContractParameter(map[StackItem]bool) smartcontract.Parameter
// Type returns stack item type.
Type() StackItemType
} }
func makeStackItem(v interface{}) StackItem { func makeStackItem(v interface{}) StackItem {
@ -177,6 +179,9 @@ func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract.
} }
} }
// Type implements StackItem interface.
func (i *StructItem) Type() StackItemType { return StructT }
// Clone returns a Struct with all Struct fields copied by value. // Clone returns a Struct with all Struct fields copied by value.
// Array fields are still copied by reference. // Array fields are still copied by reference.
func (i *StructItem) Clone() *StructItem { func (i *StructItem) Clone() *StructItem {
@ -235,6 +240,9 @@ func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramete
} }
} }
// Type implements StackItem interface.
func (i NullItem) Type() StackItemType { return AnyT }
// BigIntegerItem represents a big integer on the stack. // BigIntegerItem represents a big integer on the stack.
type BigIntegerItem struct { type BigIntegerItem struct {
value *big.Int value *big.Int
@ -300,6 +308,9 @@ func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.P
} }
} }
// Type implements StackItem interface.
func (i *BigIntegerItem) Type() StackItemType { return IntegerT }
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)
@ -380,6 +391,9 @@ func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramet
} }
} }
// Type implements StackItem interface.
func (i *BoolItem) Type() StackItemType { return BooleanT }
// ByteArrayItem represents a byte array on the stack. // ByteArrayItem represents a byte array on the stack.
type ByteArrayItem struct { type ByteArrayItem struct {
value []byte value []byte
@ -442,6 +456,9 @@ func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Pa
} }
} }
// Type implements StackItem interface.
func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT }
// ArrayItem represents a new ArrayItem object. // ArrayItem represents a new ArrayItem object.
type ArrayItem struct { type ArrayItem struct {
value []StackItem value []StackItem
@ -506,6 +523,9 @@ func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.P
} }
} }
// Type implements StackItem interface.
func (i *ArrayItem) Type() StackItemType { return ArrayT }
// MapElement is a key-value pair of StackItems. // MapElement is a key-value pair of StackItems.
type MapElement struct { type MapElement struct {
Key StackItem Key StackItem
@ -591,6 +611,9 @@ func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Par
} }
} }
// Type implements StackItem interface.
func (i *MapItem) Type() StackItemType { return MapT }
// Add adds key-value pair to the map. // Add adds key-value pair to the map.
func (i *MapItem) Add(key, value StackItem) { func (i *MapItem) Add(key, value StackItem) {
if !isValidMapKey(key) { if !isValidMapKey(key) {
@ -678,6 +701,9 @@ func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Para
} }
} }
// Type implements StackItem interface.
func (i *InteropItem) Type() StackItemType { return InteropT }
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (i *InteropItem) MarshalJSON() ([]byte, error) { func (i *InteropItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)

View file

@ -565,6 +565,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
res := v.estack.Pop().value.Equals(NullItem{}) res := v.estack.Pop().value.Equals(NullItem{})
v.estack.PushVal(res) v.estack.PushVal(res)
case opcode.ISTYPE:
res := v.estack.Pop().Item()
v.estack.PushVal(res.Type() == StackItemType(parameter[0]))
// Stack operations. // Stack operations.
case opcode.TOALTSTACK: case opcode.TOALTSTACK:
v.astack.Push(v.estack.Pop()) v.astack.Push(v.estack.Pop())

View file

@ -216,6 +216,46 @@ func TestISNULL(t *testing.T) {
}) })
} }
func testISTYPE(t *testing.T, result bool, typ StackItemType, item StackItem) {
prog := []byte{byte(opcode.ISTYPE), byte(typ)}
v := load(prog)
v.estack.PushVal(item)
runVM(t, v)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, result, v.estack.Pop().Bool())
}
func TestISTYPE(t *testing.T) {
t.Run("Integer", func(t *testing.T) {
testISTYPE(t, true, IntegerT, NewBigIntegerItem(big.NewInt(42)))
testISTYPE(t, false, IntegerT, NewByteArrayItem([]byte{}))
})
t.Run("Boolean", func(t *testing.T) {
testISTYPE(t, true, BooleanT, NewBoolItem(true))
testISTYPE(t, false, BooleanT, NewByteArrayItem([]byte{}))
})
t.Run("ByteArray", func(t *testing.T) {
testISTYPE(t, true, ByteArrayT, NewByteArrayItem([]byte{}))
testISTYPE(t, false, ByteArrayT, NewBigIntegerItem(big.NewInt(42)))
})
t.Run("Array", func(t *testing.T) {
testISTYPE(t, true, ArrayT, NewArrayItem([]StackItem{}))
testISTYPE(t, false, ArrayT, NewByteArrayItem([]byte{}))
})
t.Run("Struct", func(t *testing.T) {
testISTYPE(t, true, StructT, NewStructItem([]StackItem{}))
testISTYPE(t, false, StructT, NewByteArrayItem([]byte{}))
})
t.Run("Map", func(t *testing.T) {
testISTYPE(t, true, MapT, NewMapItem())
testISTYPE(t, false, MapT, NewByteArrayItem([]byte{}))
})
t.Run("Interop", func(t *testing.T) {
testISTYPE(t, true, InteropT, NewInteropItem(42))
testISTYPE(t, false, InteropT, NewByteArrayItem([]byte{}))
})
}
// appendBigStruct returns a program which: // appendBigStruct returns a program which:
// 1. pushes size Structs on stack // 1. pushes size Structs on stack
// 2. packs them into a new struct // 2. packs them into a new struct
@ -685,7 +725,7 @@ func TestSerializeMap(t *testing.T) {
} }
func TestSerializeMapCompat(t *testing.T) { func TestSerializeMapCompat(t *testing.T) {
resHex := "820100036b6579000576616c7565" resHex := "480128036b6579280576616c7565"
res, err := hex.DecodeString(resHex) res, err := hex.DecodeString(resHex)
require.NoError(t, err) require.NoError(t, err)