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,
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

View file

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

View file

@ -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++ {

View file

@ -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)

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{})
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())

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:
// 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)