From 3ff7fd52625a4cd1fa0b8d1bc3e1035ad59215d0 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 13 Nov 2019 14:34:03 +0300 Subject: [PATCH] vm: implement Neo.Enumerator.* interops --- pkg/core/interop_neo.go | 21 ++++++++++++ pkg/core/interops.go | 8 ++--- pkg/vm/interop.go | 56 ++++++++++++++++++++++++++++++ pkg/vm/interop_iterators.go | 44 ++++++++++++++++++++++++ pkg/vm/stack.go | 11 ++++++ pkg/vm/vm_test.go | 68 +++++++++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 pkg/vm/interop_iterators.go diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 852666740..33407e431 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -787,3 +787,24 @@ func (ic *interopContext) runtimeSerialize(v *vm.VM) error { func (ic *interopContext) runtimeDeserialize(v *vm.VM) error { return vm.RuntimeDeserialize(v) } + +// enumeratorConcat concatenates 2 enumerators into a single one. +func (ic *interopContext) enumeratorConcat(v *vm.VM) error { + return vm.EnumeratorConcat(v) +} + +// enumeratorCreate creates an enumerator from an array-like stack item. +func (ic *interopContext) enumeratorCreate(v *vm.VM) error { + return vm.EnumeratorCreate(v) +} + +// enumeratorNext advances the enumerator, pushes true if is it was successful +// and false otherwise. +func (ic *interopContext) enumeratorNext(v *vm.VM) error { + return vm.EnumeratorNext(v) +} + +// enumeratorValue returns the current value of the enumerator. +func (ic *interopContext) enumeratorValue(v *vm.VM) error { + return vm.EnumeratorValue(v) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 49472dd3e..b7c249a91 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -142,6 +142,10 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1}, {Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1}, {Name: "Neo.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0}, + {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1}, + {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1}, + {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, + {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, Price: 1}, {Name: "Neo.Header.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1}, {Name: "Neo.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1}, {Name: "Neo.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1}, @@ -178,10 +182,6 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200}, {Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200}, {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100}, - // {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1}, - // {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1}, - // {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, - // {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, Price: 1}, // {Name: "Neo.Iterator.Concat", Func: (*interopContext).iteratorConcat, Price: 1}, // {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1}, // {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1}, diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index facfcb601..3489a925a 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -40,6 +40,14 @@ var defaultVMInterops = []interopIDFuncPrice{ InteropFuncPrice{RuntimeDeserialize, 1}}, {InteropNameToID([]byte("System.Runtime.Deserialize")), InteropFuncPrice{RuntimeDeserialize, 1}}, + {InteropNameToID([]byte("Neo.Enumerator.Create")), + InteropFuncPrice{EnumeratorCreate, 1}}, + {InteropNameToID([]byte("Neo.Enumerator.Next")), + InteropFuncPrice{EnumeratorNext, 1}}, + {InteropNameToID([]byte("Neo.Enumerator.Concat")), + InteropFuncPrice{EnumeratorConcat, 1}}, + {InteropNameToID([]byte("Neo.Enumerator.Value")), + InteropFuncPrice{EnumeratorValue, 1}}, } func getDefaultVMInterop(id uint32) *InteropFuncPrice { @@ -107,3 +115,51 @@ func init() { return defaultVMInterops[i].ID < defaultVMInterops[j].ID }) } + +// EnumeratorCreate handles syscall Neo.Enumerator.Create. +func EnumeratorCreate(v *VM) error { + data := v.Estack().Pop().Array() + v.Estack().Push(&Element{ + value: NewInteropItem(&arrayWrapper{ + index: -1, + value: data, + }), + }) + + return nil +} + +// EnumeratorNext handles syscall Neo.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 Neo.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 Neo.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: NewInteropItem(&concatEnum{ + current: arr1, + second: arr2, + }), + }) + + return nil +} diff --git a/pkg/vm/interop_iterators.go b/pkg/vm/interop_iterators.go new file mode 100644 index 000000000..cb1254874 --- /dev/null +++ b/pkg/vm/interop_iterators.go @@ -0,0 +1,44 @@ +package vm + +type ( + enumerator interface { + Next() bool + Value() StackItem + } + + arrayWrapper struct { + index int + value []StackItem + } + + concatEnum struct { + current enumerator + second enumerator + } +) + +func (a *arrayWrapper) Next() bool { + if next := a.index + 1; next < len(a.value) { + a.index = next + return true + } + + return false +} + +func (a *arrayWrapper) Value() StackItem { + return a.value[a.index] +} + +func (c *concatEnum) Next() bool { + if c.current.Next() { + return true + } + c.current = c.second + + return c.current.Next() +} + +func (c *concatEnum) Value() StackItem { + return c.current.Value() +} diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index dc4ecf0e0..b0a2f72d9 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -155,6 +155,17 @@ func (e *Element) Array() []StackItem { } } +// Interop attempts to get the underlying value of the element +// as an interop item. +func (e *Element) Interop() *InteropItem { + switch t := e.value.(type) { + case *InteropItem: + return t + default: + panic("element is not an interop") + } +} + // Stack represents a Stack backed by a double linked list. type Stack struct { top Element diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 5825fbc83..ae211486f 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -333,6 +333,74 @@ func TestPushData4Good(t *testing.T) { assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes()) } +func getEnumeratorProg(n int) (prog []byte) { + prog = append(prog, byte(opcode.TOALTSTACK)) + for i := 0; i < n; i++ { + prog = append(prog, byte(opcode.DUPFROMALTSTACK)) + prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...) + prog = append(prog, byte(opcode.DUPFROMALTSTACK)) + prog = append(prog, getSyscallProg("Neo.Enumerator.Value")...) + } + prog = append(prog, byte(opcode.DUPFROMALTSTACK)) + prog = append(prog, getSyscallProg("Neo.Enumerator.Next")...) + + return +} + +func checkEnumeratorStack(t *testing.T, vm *VM, arr []StackItem) { + require.Equal(t, len(arr)+1, vm.estack.Len()) + require.Equal(t, NewBoolItem(false), vm.estack.Peek(0).value) + for i := 0; i < len(arr); i++ { + require.Equal(t, arr[i], vm.estack.Peek(i+1).value, "pos: %d", i+1) + } +} + +func testIterableCreate(t *testing.T, typ string) { + prog := getSyscallProg("Neo." + typ + ".Create") + prog = append(prog, getEnumeratorProg(2)...) + + vm := load(prog) + arr := []StackItem{ + NewBigIntegerItem(42), + NewByteArrayItem([]byte{3, 2, 1}), + } + vm.estack.Push(&Element{value: NewArrayItem(arr)}) + + runVM(t, vm) + checkEnumeratorStack(t, vm, []StackItem{ + arr[1], NewBoolItem(true), + arr[0], NewBoolItem(true), + }) +} + +func TestEnumeratorCreate(t *testing.T) { + testIterableCreate(t, "Enumerator") +} + +func TestEnumeratorConcat(t *testing.T) { + prog := getSyscallProg("Neo.Enumerator.Create") + prog = append(prog, byte(opcode.SWAP)) + prog = append(prog, getSyscallProg("Neo.Enumerator.Create")...) + prog = append(prog, getSyscallProg("Neo.Enumerator.Concat")...) + prog = append(prog, getEnumeratorProg(3)...) + vm := load(prog) + + arr := []StackItem{ + NewBoolItem(false), + NewBigIntegerItem(123), + NewMapItem(), + } + vm.estack.Push(&Element{value: NewArrayItem(arr[:1])}) + vm.estack.Push(&Element{value: NewArrayItem(arr[1:])}) + + runVM(t, vm) + checkEnumeratorStack(t, vm, []StackItem{ + arr[2], NewBoolItem(true), + arr[1], NewBoolItem(true), + arr[0], NewBoolItem(true), + }) +} + func getSyscallProg(name string) (prog []byte) { prog = []byte{byte(opcode.SYSCALL)} prog = append(prog, byte(len(name)))