vm: implement Neo.Enumerator.* interops
This commit is contained in:
parent
f92e84c4cf
commit
3ff7fd5262
6 changed files with 204 additions and 4 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
44
pkg/vm/interop_iterators.go
Normal file
44
pkg/vm/interop_iterators.go
Normal file
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
|
|
Loading…
Reference in a new issue