vm: implement ISTYPE opcode
Also make StackItemType public and reorder it according to NEO3.
This commit is contained in:
parent
48a41bd737
commit
d3b9aef8e2
6 changed files with 142 additions and 22 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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++ {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue