forked from TrueCloudLab/neoneo-go
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,
|
||||
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
|
||||
|
|
|
@ -162,6 +162,9 @@ const (
|
|||
REMOVE Opcode = 0xD2
|
||||
CLEARITEMS Opcode = 0xD3
|
||||
|
||||
// Types
|
||||
ISTYPE Opcode = 0xD9
|
||||
|
||||
// Exceptions
|
||||
THROW Opcode = 0xF0
|
||||
THROWIFNOT Opcode = 0xF1
|
||||
|
|
|
@ -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++ {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue