neo-go/pkg/vm/interop.go
2020-08-14 14:21:54 +03:00

261 lines
7 KiB
Go

package vm
import (
"errors"
"fmt"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// interopIDFuncPrice adds an ID to the InteropFuncPrice.
type interopIDFuncPrice struct {
ID uint32
Func func(vm *VM) error
Price int64
RequiredFlags smartcontract.CallFlag
}
var defaultVMInterops = []interopIDFuncPrice{
{ID: interopnames.ToID([]byte(interopnames.SystemBinaryDeserialize)),
Func: RuntimeDeserialize, Price: 500000},
{ID: interopnames.ToID([]byte(interopnames.SystemBinarySerialize)),
Func: RuntimeSerialize, Price: 100000},
{ID: interopnames.ToID([]byte(interopnames.SystemRuntimeLog)),
Func: runtimeLog, Price: 1000000, RequiredFlags: smartcontract.AllowNotify},
{ID: interopnames.ToID([]byte(interopnames.SystemRuntimeNotify)),
Func: runtimeNotify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify},
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorCreate)),
Func: EnumeratorCreate, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorNext)),
Func: EnumeratorNext, Price: 1000000},
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorConcat)),
Func: EnumeratorConcat, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorValue)),
Func: EnumeratorValue, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorCreate)),
Func: IteratorCreate, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorConcat)),
Func: IteratorConcat, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorKey)),
Func: IteratorKey, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorKeys)),
Func: IteratorKeys, Price: 400},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorValues)),
Func: IteratorValues, Price: 400},
}
func init() {
sort.Slice(defaultVMInterops, func(i, j int) bool { return defaultVMInterops[i].ID < defaultVMInterops[j].ID })
}
func defaultSyscallHandler(v *VM, id uint32) error {
n := sort.Search(len(defaultVMInterops), func(i int) bool {
return defaultVMInterops[i].ID >= id
})
if n >= len(defaultVMInterops) || defaultVMInterops[n].ID != id {
return errors.New("syscall not found")
}
d := defaultVMInterops[n]
if !v.Context().callFlag.Has(d.RequiredFlags) {
return fmt.Errorf("missing call flags: %05b vs %05b", v.Context().callFlag, d.RequiredFlags)
}
return d.Func(v)
}
// runtimeLog handles the syscall "System.Runtime.Log" for printing and logging stuff.
func runtimeLog(vm *VM) error {
msg := vm.Estack().Pop().String()
fmt.Printf("NEO-GO-VM (log) > %s\n", msg)
return nil
}
// runtimeNotify handles the syscall "System.Runtime.Notify" for printing and logging stuff.
func runtimeNotify(vm *VM) error {
name := vm.Estack().Pop().String()
item := vm.Estack().Pop()
fmt.Printf("NEO-GO-VM (notify) > [%s] %s\n", name, item.Value())
return nil
}
// RuntimeSerialize handles System.Binary.Serialize syscall.
func RuntimeSerialize(vm *VM) error {
item := vm.Estack().Pop()
data, err := stackitem.SerializeItem(item.value)
if err != nil {
return err
} else if len(data) > stackitem.MaxSize {
return errors.New("too big item")
}
vm.Estack().PushVal(data)
return nil
}
// RuntimeDeserialize handles System.Binary.Deserialize syscall.
func RuntimeDeserialize(vm *VM) error {
data := vm.Estack().Pop().Bytes()
item, err := stackitem.DeserializeItem(data)
if err != nil {
return err
}
vm.Estack().Push(&Element{value: item})
return nil
}
// init sorts the global defaultVMInterops value.
func init() {
sort.Slice(defaultVMInterops, func(i, j int) bool {
return defaultVMInterops[i].ID < defaultVMInterops[j].ID
})
}
// EnumeratorCreate handles syscall System.Enumerator.Create.
func EnumeratorCreate(v *VM) error {
var interop interface{}
switch t := v.Estack().Pop().value.(type) {
case *stackitem.Array, *stackitem.Struct:
interop = &arrayWrapper{
index: -1,
value: t.Value().([]stackitem.Item),
}
default:
data, err := t.TryBytes()
if err != nil {
return fmt.Errorf("can not create enumerator from type %s: %w", t.Type(), err)
}
interop = &byteArrayWrapper{
index: -1,
value: data,
}
}
v.Estack().Push(&Element{
value: stackitem.NewInterop(interop),
})
return nil
}
// EnumeratorNext handles syscall System.Enumerator.Next.
func EnumeratorNext(v *VM) error {
iop := v.Estack().Pop().Interop()
arr := iop.Value().(enumerator)
v.Estack().PushVal(arr.Next())
return nil
}
// EnumeratorValue handles syscall System.Enumerator.Value.
func EnumeratorValue(v *VM) error {
iop := v.Estack().Pop().Interop()
arr := iop.Value().(enumerator)
v.Estack().Push(&Element{value: arr.Value()})
return nil
}
// EnumeratorConcat handles syscall System.Enumerator.Concat.
func EnumeratorConcat(v *VM) error {
iop1 := v.Estack().Pop().Interop()
arr1 := iop1.Value().(enumerator)
iop2 := v.Estack().Pop().Interop()
arr2 := iop2.Value().(enumerator)
v.Estack().Push(&Element{
value: stackitem.NewInterop(&concatEnum{
current: arr1,
second: arr2,
}),
})
return nil
}
// IteratorCreate handles syscall System.Iterator.Create.
func IteratorCreate(v *VM) error {
data := v.Estack().Pop()
var item stackitem.Item
switch t := data.value.(type) {
case *stackitem.Array, *stackitem.Struct:
item = stackitem.NewInterop(&arrayWrapper{
index: -1,
value: t.Value().([]stackitem.Item),
})
case *stackitem.Map:
item = NewMapIterator(t)
default:
data, err := t.TryBytes()
if err != nil {
return fmt.Errorf("non-iterable type %s", t.Type())
}
item = stackitem.NewInterop(&byteArrayWrapper{
index: -1,
value: data,
})
}
v.Estack().Push(&Element{value: item})
return nil
}
// NewMapIterator returns new interop item containing iterator over m.
func NewMapIterator(m *stackitem.Map) *stackitem.Interop {
return stackitem.NewInterop(&mapWrapper{
index: -1,
m: m.Value().([]stackitem.MapElement),
})
}
// IteratorConcat handles syscall System.Iterator.Concat.
func IteratorConcat(v *VM) error {
iop1 := v.Estack().Pop().Interop()
iter1 := iop1.Value().(iterator)
iop2 := v.Estack().Pop().Interop()
iter2 := iop2.Value().(iterator)
v.Estack().Push(&Element{value: stackitem.NewInterop(
&concatIter{
current: iter1,
second: iter2,
},
)})
return nil
}
// IteratorKey handles syscall System.Iterator.Key.
func IteratorKey(v *VM) error {
iop := v.estack.Pop().Interop()
iter := iop.Value().(iterator)
v.Estack().Push(&Element{value: iter.Key()})
return nil
}
// IteratorKeys handles syscall System.Iterator.Keys.
func IteratorKeys(v *VM) error {
iop := v.estack.Pop().Interop()
iter := iop.Value().(iterator)
v.Estack().Push(&Element{value: stackitem.NewInterop(
&keysWrapper{iter},
)})
return nil
}
// IteratorValues handles syscall System.Iterator.Values.
func IteratorValues(v *VM) error {
iop := v.estack.Pop().Interop()
iter := iop.Value().(iterator)
v.Estack().Push(&Element{value: stackitem.NewInterop(
&valuesWrapper{iter},
)})
return nil
}