diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 3e67bc6f5..362922bf0 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -101,7 +101,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) { } case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, - opcode.CALL: + opcode.CALL, opcode.ISTYPE: numtoread = 1 case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, 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") } +// Type implements StackItem interface. +func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") } + // Equals implements StackItem interface. func (c *Context) Equals(s StackItem) bool { return c == s diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index 098f50adc..52e258d88 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -162,6 +162,9 @@ const ( REMOVE Opcode = 0xD2 CLEARITEMS Opcode = 0xD3 + // Types + ISTYPE Opcode = 0xD9 + // Exceptions THROW Opcode = 0xF0 THROWIFNOT Opcode = 0xF1 diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go index a3d30941f..23c68f494 100644 --- a/pkg/vm/serialization.go +++ b/pkg/vm/serialization.go @@ -7,17 +7,61 @@ import ( "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 ( - byteArrayT stackItemType = 0x00 - booleanT stackItemType = 0x01 - integerT stackItemType = 0x02 - arrayT stackItemType = 0x80 - structT stackItemType = 0x81 - mapT stackItemType = 0x82 + AnyT StackItemType = 0x00 + PointerT StackItemType = 0x10 + BooleanT StackItemType = 0x20 + IntegerT StackItemType = 0x21 + ByteArrayT StackItemType = 0x28 + 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. func SerializeItem(item StackItem) ([]byte, error) { w := io.NewBufBinWriter() @@ -43,13 +87,13 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { switch t := item.(type) { case *ByteArrayItem: - w.WriteBytes([]byte{byte(byteArrayT)}) + w.WriteBytes([]byte{byte(ByteArrayT)}) w.WriteVarBytes(t.value) case *BoolItem: - w.WriteBytes([]byte{byte(booleanT)}) + w.WriteBytes([]byte{byte(BooleanT)}) w.WriteBool(t.value) case *BigIntegerItem: - w.WriteBytes([]byte{byte(integerT)}) + w.WriteBytes([]byte{byte(IntegerT)}) w.WriteVarBytes(emit.IntToBytes(t.value)) case *InteropItem: 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) if isArray { - w.WriteBytes([]byte{byte(arrayT)}) + w.WriteBytes([]byte{byte(ArrayT)}) } else { - w.WriteBytes([]byte{byte(structT)}) + w.WriteBytes([]byte{byte(StructT)}) } arr := t.Value().([]StackItem) @@ -71,7 +115,7 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { case *MapItem: seen[item] = true - w.WriteBytes([]byte{byte(mapT)}) + w.WriteBytes([]byte{byte(MapT)}) w.WriteVarUint(uint64(len(t.value))) for i := range t.value { serializeItemTo(t.value[i].Key, w, seen) @@ -100,31 +144,31 @@ func DecodeBinaryStackItem(r *io.BinReader) StackItem { return nil } - switch stackItemType(t) { - case byteArrayT: + switch StackItemType(t) { + case ByteArrayT: data := r.ReadVarBytes() return NewByteArrayItem(data) - case booleanT: + case BooleanT: var b = r.ReadBool() return NewBoolItem(b) - case integerT: + case IntegerT: data := r.ReadVarBytes() num := emit.BytesToInt(data) return &BigIntegerItem{ value: num, } - case arrayT, structT: + case ArrayT, StructT: size := int(r.ReadVarUint()) arr := make([]StackItem, size) for i := 0; i < size; i++ { arr[i] = DecodeBinaryStackItem(r) } - if stackItemType(t) == arrayT { + if StackItemType(t) == ArrayT { return &ArrayItem{value: arr} } return &StructItem{value: arr} - case mapT: + case MapT: size := int(r.ReadVarUint()) m := NewMapItem() for i := 0; i < size; i++ { diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index b6ed046a4..fbe80346f 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -28,6 +28,8 @@ type StackItem interface { Equals(s StackItem) bool // ToContractParameter converts StackItem to smartcontract.Parameter ToContractParameter(map[StackItem]bool) smartcontract.Parameter + // Type returns stack item type. + Type() StackItemType } 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. // Array fields are still copied by reference. 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. type BigIntegerItem struct { 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. func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { 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. type ByteArrayItem struct { 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. type ArrayItem struct { 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. type MapElement struct { 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. func (i *MapItem) Add(key, value StackItem) { 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. func (i *InteropItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 730fc6bc7..4682b37ac 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -565,6 +565,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro res := v.estack.Pop().value.Equals(NullItem{}) v.estack.PushVal(res) + case opcode.ISTYPE: + res := v.estack.Pop().Item() + v.estack.PushVal(res.Type() == StackItemType(parameter[0])) + // Stack operations. case opcode.TOALTSTACK: v.astack.Push(v.estack.Pop()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 590c11f25..e57e97e80 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -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: // 1. pushes size Structs on stack // 2. packs them into a new struct @@ -685,7 +725,7 @@ func TestSerializeMap(t *testing.T) { } func TestSerializeMapCompat(t *testing.T) { - resHex := "820100036b6579000576616c7565" + resHex := "480128036b6579280576616c7565" res, err := hex.DecodeString(resHex) require.NoError(t, err)