mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 23:25:22 +00:00
vm: implements CONVERT opcode
This commit is contained in:
parent
be38798785
commit
7e98a2ffa0
7 changed files with 253 additions and 8 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.ISTYPE, opcode.NEWARRAYT:
|
||||
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT:
|
||||
numtoread = 1
|
||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
||||
|
@ -192,6 +192,11 @@ func (c *Context) TryInteger() (*big.Int, error) {
|
|||
// Type implements StackItem interface.
|
||||
func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (c *Context) Convert(_ StackItemType) (StackItem, error) {
|
||||
panic("Context cannot be converted to anything")
|
||||
}
|
||||
|
||||
// Equals implements StackItem interface.
|
||||
func (c *Context) Equals(s StackItem) bool {
|
||||
return c == s
|
||||
|
|
|
@ -161,8 +161,9 @@ const (
|
|||
CLEARITEMS Opcode = 0xD3
|
||||
|
||||
// Types
|
||||
ISNULL Opcode = 0xD8
|
||||
ISTYPE Opcode = 0xD9
|
||||
ISNULL Opcode = 0xD8
|
||||
ISTYPE Opcode = 0xD9
|
||||
CONVERT Opcode = 0xDB
|
||||
|
||||
// Exceptions
|
||||
THROW Opcode = 0xF0
|
||||
|
|
|
@ -141,11 +141,12 @@ func _() {
|
|||
_ = x[CLEARITEMS-211]
|
||||
_ = x[ISNULL-216]
|
||||
_ = x[ISTYPE-217]
|
||||
_ = x[CONVERT-219]
|
||||
_ = x[THROW-240]
|
||||
_ = x[THROWIFNOT-241]
|
||||
}
|
||||
|
||||
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPETHROWTHROWIFNOT"
|
||||
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERTTHROWTHROWIFNOT"
|
||||
|
||||
var _Opcode_map = map[Opcode]string{
|
||||
0: _Opcode_name[0:8],
|
||||
|
@ -279,8 +280,9 @@ var _Opcode_map = map[Opcode]string{
|
|||
211: _Opcode_name[731:741],
|
||||
216: _Opcode_name[741:747],
|
||||
217: _Opcode_name[747:753],
|
||||
240: _Opcode_name[753:758],
|
||||
241: _Opcode_name[758:768],
|
||||
219: _Opcode_name[753:760],
|
||||
240: _Opcode_name[760:765],
|
||||
241: _Opcode_name[765:775],
|
||||
}
|
||||
|
||||
func (i Opcode) String() string {
|
||||
|
|
|
@ -80,8 +80,7 @@ func (e *Element) BigInt() *big.Int {
|
|||
return val
|
||||
}
|
||||
|
||||
// Bool attempts to get the underlying value of the element as a boolean.
|
||||
// Will panic if the assertion failed which will be caught by the VM.
|
||||
// Bool converts an underlying value of the element to a boolean.
|
||||
func (e *Element) Bool() bool {
|
||||
return e.value.Bool()
|
||||
}
|
||||
|
|
|
@ -32,8 +32,12 @@ type StackItem interface {
|
|||
ToContractParameter(map[StackItem]bool) smartcontract.Parameter
|
||||
// Type returns stack item type.
|
||||
Type() StackItemType
|
||||
// Convert converts StackItem to another type.
|
||||
Convert(StackItemType) (StackItem, error)
|
||||
}
|
||||
|
||||
var errInvalidConversion = errors.New("invalid conversion type")
|
||||
|
||||
func makeStackItem(v interface{}) StackItem {
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
|
@ -108,6 +112,33 @@ func makeStackItem(v interface{}) StackItem {
|
|||
}
|
||||
}
|
||||
|
||||
// convertPrimitive converts primitive item to a specified type.
|
||||
func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) {
|
||||
if item.Type() == typ {
|
||||
return item, nil
|
||||
}
|
||||
switch typ {
|
||||
case IntegerT:
|
||||
bi, err := item.TryInteger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewBigIntegerItem(bi), nil
|
||||
case ByteArrayT:
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewByteArrayItem(b), nil
|
||||
case BufferT:
|
||||
panic("TODO") // #877
|
||||
case BooleanT:
|
||||
return NewBoolItem(item.Bool()), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// StructItem represents a struct on the stack.
|
||||
type StructItem struct {
|
||||
value []StackItem
|
||||
|
@ -187,6 +218,20 @@ func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract.
|
|||
// Type implements StackItem interface.
|
||||
func (i *StructItem) Type() StackItemType { return StructT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *StructItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
switch typ {
|
||||
case StructT:
|
||||
return i, nil
|
||||
case ArrayT:
|
||||
return NewArrayItem(i.value), nil
|
||||
case BooleanT:
|
||||
return NewBoolItem(i.Bool()), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a Struct with all Struct fields copied by value.
|
||||
// Array fields are still copied by reference.
|
||||
func (i *StructItem) Clone() *StructItem {
|
||||
|
@ -251,6 +296,14 @@ func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramete
|
|||
// Type implements StackItem interface.
|
||||
func (i NullItem) Type() StackItemType { return AnyT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i NullItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
if typ == AnyT || !typ.IsValid() {
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// BigIntegerItem represents a big integer on the stack.
|
||||
type BigIntegerItem struct {
|
||||
value *big.Int
|
||||
|
@ -324,6 +377,11 @@ func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.P
|
|||
// Type implements StackItem interface.
|
||||
func (i *BigIntegerItem) Type() StackItemType { return IntegerT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *BigIntegerItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
return convertPrimitive(i, typ)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
@ -410,6 +468,11 @@ func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramet
|
|||
// Type implements StackItem interface.
|
||||
func (i *BoolItem) Type() StackItemType { return BooleanT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *BoolItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
return convertPrimitive(i, typ)
|
||||
}
|
||||
|
||||
// ByteArrayItem represents a byte array on the stack.
|
||||
type ByteArrayItem struct {
|
||||
value []byte
|
||||
|
@ -488,6 +551,11 @@ func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Pa
|
|||
// Type implements StackItem interface.
|
||||
func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *ByteArrayItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
return convertPrimitive(i, typ)
|
||||
}
|
||||
|
||||
// ArrayItem represents a new ArrayItem object.
|
||||
type ArrayItem struct {
|
||||
value []StackItem
|
||||
|
@ -558,6 +626,20 @@ func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.P
|
|||
// Type implements StackItem interface.
|
||||
func (i *ArrayItem) Type() StackItemType { return ArrayT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *ArrayItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
switch typ {
|
||||
case ArrayT:
|
||||
return i, nil
|
||||
case StructT:
|
||||
return NewStructItem(i.value), nil
|
||||
case BooleanT:
|
||||
return NewBoolItem(i.Bool()), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// MapElement is a key-value pair of StackItems.
|
||||
type MapElement struct {
|
||||
Key StackItem
|
||||
|
@ -649,6 +731,18 @@ func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Par
|
|||
// Type implements StackItem interface.
|
||||
func (i *MapItem) Type() StackItemType { return MapT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *MapItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
switch typ {
|
||||
case MapT:
|
||||
return i, nil
|
||||
case BooleanT:
|
||||
return NewBoolItem(i.Bool()), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds key-value pair to the map.
|
||||
func (i *MapItem) Add(key, value StackItem) {
|
||||
if !isValidMapKey(key) {
|
||||
|
@ -742,6 +836,18 @@ func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Para
|
|||
// Type implements StackItem interface.
|
||||
func (i *InteropItem) Type() StackItemType { return InteropT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *InteropItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
switch typ {
|
||||
case InteropT:
|
||||
return i, nil
|
||||
case BooleanT:
|
||||
return NewBoolItem(i.Bool()), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *InteropItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
|
|
@ -569,6 +569,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
res := v.estack.Pop().Item()
|
||||
v.estack.PushVal(res.Type() == StackItemType(parameter[0]))
|
||||
|
||||
case opcode.CONVERT:
|
||||
typ := StackItemType(parameter[0])
|
||||
item := v.estack.Pop().Item()
|
||||
result, err := item.Convert(typ)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.estack.PushVal(result)
|
||||
|
||||
// Stack operations.
|
||||
case opcode.TOALTSTACK:
|
||||
v.astack.Push(v.estack.Pop())
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
@ -256,6 +257,128 @@ func TestISTYPE(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func testCONVERT(isErr bool, to StackItemType, item, res StackItem) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
prog := []byte{byte(opcode.CONVERT), byte(to)}
|
||||
v := load(prog)
|
||||
v.estack.PushVal(item)
|
||||
if isErr {
|
||||
checkVMFailed(t, v)
|
||||
return
|
||||
}
|
||||
runVM(t, v)
|
||||
require.Equal(t, 1, v.estack.Len())
|
||||
require.Equal(t, res, v.estack.Pop().value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCONVERT(t *testing.T) {
|
||||
type convertTC struct {
|
||||
item, res StackItem
|
||||
}
|
||||
arr := []StackItem{
|
||||
NewBigIntegerItem(big.NewInt(7)),
|
||||
NewByteArrayItem([]byte{4, 8, 15}),
|
||||
}
|
||||
m := NewMapItem()
|
||||
m.Add(NewByteArrayItem([]byte{1}), NewByteArrayItem([]byte{2}))
|
||||
|
||||
getName := func(item StackItem, typ StackItemType) string { return fmt.Sprintf("%s->%s", item, typ) }
|
||||
|
||||
t.Run("->Bool", func(t *testing.T) {
|
||||
testBool := func(a, b StackItem) func(t *testing.T) {
|
||||
return testCONVERT(false, BooleanT, a, b)
|
||||
}
|
||||
|
||||
trueCases := []StackItem{
|
||||
NewBoolItem(true), NewBigIntegerItem(big.NewInt(11)), NewByteArrayItem([]byte{1, 2, 3}),
|
||||
NewArrayItem(arr), NewArrayItem(nil),
|
||||
NewStructItem(arr), NewStructItem(nil),
|
||||
NewMapItem(), m, NewInteropItem(struct{}{}),
|
||||
}
|
||||
for i := range trueCases {
|
||||
t.Run(getName(trueCases[i], BooleanT), testBool(trueCases[i], NewBoolItem(true)))
|
||||
}
|
||||
|
||||
falseCases := []StackItem{
|
||||
NewBigIntegerItem(big.NewInt(0)), NewByteArrayItem([]byte{0, 0}), NewBoolItem(false),
|
||||
}
|
||||
for i := range falseCases {
|
||||
testBool(falseCases[i], NewBoolItem(false))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("compound/interop -> basic", func(t *testing.T) {
|
||||
types := []StackItemType{IntegerT, ByteArrayT}
|
||||
items := []StackItem{NewArrayItem(nil), NewStructItem(nil), NewMapItem(), NewInteropItem(struct{}{})}
|
||||
for _, typ := range types {
|
||||
for j := range items {
|
||||
t.Run(getName(items[j], typ), testCONVERT(true, typ, items[j], nil))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("primitive -> Integer/ByteArray", func(t *testing.T) {
|
||||
n := big.NewInt(42)
|
||||
b := emit.IntToBytes(n)
|
||||
|
||||
itemInt := NewBigIntegerItem(n)
|
||||
itemBytes := NewByteArrayItem(b)
|
||||
|
||||
trueCases := map[StackItemType][]convertTC{
|
||||
IntegerT: {
|
||||
{itemInt, itemInt},
|
||||
{itemBytes, itemInt},
|
||||
{NewBoolItem(true), NewBigIntegerItem(big.NewInt(1))},
|
||||
{NewBoolItem(false), NewBigIntegerItem(big.NewInt(0))},
|
||||
},
|
||||
ByteArrayT: {
|
||||
{itemInt, itemBytes},
|
||||
{itemBytes, itemBytes},
|
||||
{NewBoolItem(true), NewByteArrayItem([]byte{1})},
|
||||
{NewBoolItem(false), NewByteArrayItem([]byte{0})},
|
||||
},
|
||||
}
|
||||
|
||||
for typ := range trueCases {
|
||||
for _, tc := range trueCases[typ] {
|
||||
t.Run(getName(tc.item, typ), testCONVERT(false, typ, tc.item, tc.res))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Struct<->Array", func(t *testing.T) {
|
||||
arrayItem := NewArrayItem(arr)
|
||||
structItem := NewStructItem(arr)
|
||||
t.Run("Array->Array", testCONVERT(false, ArrayT, arrayItem, arrayItem))
|
||||
t.Run("Array->Struct", testCONVERT(false, StructT, arrayItem, structItem))
|
||||
t.Run("Struct->Array", testCONVERT(false, ArrayT, structItem, arrayItem))
|
||||
t.Run("Struct->Struct", testCONVERT(false, StructT, structItem, structItem))
|
||||
})
|
||||
|
||||
t.Run("Map->Map", testCONVERT(false, MapT, m, m))
|
||||
|
||||
t.Run("Null->", func(t *testing.T) {
|
||||
types := []StackItemType{
|
||||
BooleanT, ByteArrayT, IntegerT, ArrayT, StructT, MapT, InteropT,
|
||||
}
|
||||
for i := range types {
|
||||
t.Run(types[i].String(), testCONVERT(false, types[i], NullItem{}, NullItem{}))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("->Any", func(t *testing.T) {
|
||||
items := []StackItem{
|
||||
NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{1}), NewBoolItem(true),
|
||||
NewArrayItem(arr), NewStructItem(arr), m, NewInteropItem(struct{}{}),
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
t.Run(items[i].String(), testCONVERT(true, AnyT, items[i], nil))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// appendBigStruct returns a program which:
|
||||
// 1. pushes size Structs on stack
|
||||
// 2. packs them into a new struct
|
||||
|
|
Loading…
Reference in a new issue