Merge pull request #493 from nspcc-dev/feature/422
vm: implement Neo.Enumerator/Iterator.* interops Partially implements #422.
This commit is contained in:
commit
f3391f8576
6 changed files with 489 additions and 11 deletions
|
@ -787,3 +787,49 @@ func (ic *interopContext) runtimeSerialize(v *vm.VM) error {
|
||||||
func (ic *interopContext) runtimeDeserialize(v *vm.VM) error {
|
func (ic *interopContext) runtimeDeserialize(v *vm.VM) error {
|
||||||
return vm.RuntimeDeserialize(v)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratorConcat concatenates 2 iterators into a single one.
|
||||||
|
func (ic *interopContext) iteratorConcat(v *vm.VM) error {
|
||||||
|
return vm.IteratorConcat(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratorCreate creates an iterator from array-like or map stack item.
|
||||||
|
func (ic *interopContext) iteratorCreate(v *vm.VM) error {
|
||||||
|
return vm.IteratorCreate(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratorKey returns current iterator key.
|
||||||
|
func (ic *interopContext) iteratorKey(v *vm.VM) error {
|
||||||
|
return vm.IteratorKey(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratorKeys returns keys of the iterator.
|
||||||
|
func (ic *interopContext) iteratorKeys(v *vm.VM) error {
|
||||||
|
return vm.IteratorKeys(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratorValues returns values of the iterator.
|
||||||
|
func (ic *interopContext) iteratorValues(v *vm.VM) error {
|
||||||
|
return vm.IteratorValues(v)
|
||||||
|
}
|
||||||
|
|
|
@ -142,6 +142,10 @@ var neoInterops = []interopedFunction{
|
||||||
{Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
|
{Name: "Neo.Contract.GetStorageContext", Func: (*interopContext).contractGetStorageContext, Price: 1},
|
||||||
{Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1},
|
{Name: "Neo.Contract.IsPayable", Func: (*interopContext).contractIsPayable, Price: 1},
|
||||||
{Name: "Neo.Contract.Migrate", Func: (*interopContext).contractMigrate, Price: 0},
|
{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.GetConsensusData", Func: (*interopContext).headerGetConsensusData, Price: 1},
|
||||||
{Name: "Neo.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
|
{Name: "Neo.Header.GetHash", Func: (*interopContext).headerGetHash, Price: 1},
|
||||||
{Name: "Neo.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
|
{Name: "Neo.Header.GetIndex", Func: (*interopContext).headerGetIndex, Price: 1},
|
||||||
|
@ -153,6 +157,11 @@ var neoInterops = []interopedFunction{
|
||||||
{Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
|
{Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
|
||||||
{Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
|
{Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1},
|
||||||
{Name: "Neo.InvocationTransaction.GetScript", Func: (*interopContext).invocationTxGetScript, Price: 1},
|
{Name: "Neo.InvocationTransaction.GetScript", Func: (*interopContext).invocationTxGetScript, 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},
|
||||||
|
{Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
|
||||||
|
{Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, Price: 1},
|
||||||
{Name: "Neo.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
|
{Name: "Neo.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
|
||||||
{Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
|
{Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
|
||||||
{Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
|
{Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
|
||||||
|
@ -178,20 +187,11 @@ var neoInterops = []interopedFunction{
|
||||||
{Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
|
{Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
|
||||||
{Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200},
|
{Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200},
|
||||||
{Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100},
|
{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},
|
|
||||||
// {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
|
|
||||||
// {Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, Price: 1},
|
|
||||||
// {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1},
|
// {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1},
|
||||||
|
|
||||||
// Aliases.
|
// Aliases.
|
||||||
// {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
|
{Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
|
||||||
// {Name: "Neo.Iterator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
|
{Name: "Neo.Iterator.Value", Func: (*interopContext).enumeratorValue, Price: 1},
|
||||||
|
|
||||||
// Old compatibility APIs.
|
// Old compatibility APIs.
|
||||||
{Name: "AntShares.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
|
{Name: "AntShares.Account.GetBalance", Func: (*interopContext).accountGetBalance, Price: 1},
|
||||||
|
|
|
@ -40,6 +40,24 @@ var defaultVMInterops = []interopIDFuncPrice{
|
||||||
InteropFuncPrice{RuntimeDeserialize, 1}},
|
InteropFuncPrice{RuntimeDeserialize, 1}},
|
||||||
{InteropNameToID([]byte("System.Runtime.Deserialize")),
|
{InteropNameToID([]byte("System.Runtime.Deserialize")),
|
||||||
InteropFuncPrice{RuntimeDeserialize, 1}},
|
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}},
|
||||||
|
{InteropNameToID([]byte("Neo.Iterator.Create")),
|
||||||
|
InteropFuncPrice{IteratorCreate, 1}},
|
||||||
|
{InteropNameToID([]byte("Neo.Iterator.Concat")),
|
||||||
|
InteropFuncPrice{IteratorConcat, 1}},
|
||||||
|
{InteropNameToID([]byte("Neo.Iterator.Key")),
|
||||||
|
InteropFuncPrice{IteratorKey, 1}},
|
||||||
|
{InteropNameToID([]byte("Neo.Iterator.Keys")),
|
||||||
|
InteropFuncPrice{IteratorKeys, 1}},
|
||||||
|
{InteropNameToID([]byte("Neo.Iterator.Values")),
|
||||||
|
InteropFuncPrice{IteratorValues, 1}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultVMInterop(id uint32) *InteropFuncPrice {
|
func getDefaultVMInterop(id uint32) *InteropFuncPrice {
|
||||||
|
@ -107,3 +125,128 @@ func init() {
|
||||||
return defaultVMInterops[i].ID < defaultVMInterops[j].ID
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// IteratorCreate handles syscall Neo.Iterator.Create.
|
||||||
|
func IteratorCreate(v *VM) error {
|
||||||
|
data := v.Estack().Pop()
|
||||||
|
var item interface{}
|
||||||
|
switch t := data.value.(type) {
|
||||||
|
case *ArrayItem, *StructItem:
|
||||||
|
item = &arrayWrapper{
|
||||||
|
index: -1,
|
||||||
|
value: t.Value().([]StackItem),
|
||||||
|
}
|
||||||
|
case *MapItem:
|
||||||
|
keys := make([]interface{}, 0, len(t.value))
|
||||||
|
for k := range t.value {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
item = &mapWrapper{
|
||||||
|
index: -1,
|
||||||
|
keys: keys,
|
||||||
|
m: t.value,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("non-iterable type")
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Estack().Push(&Element{value: NewInteropItem(item)})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IteratorConcat handles syscall Neo.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: NewInteropItem(
|
||||||
|
&concatIter{
|
||||||
|
current: iter1,
|
||||||
|
second: iter2,
|
||||||
|
},
|
||||||
|
)})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IteratorKey handles syscall Neo.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 Neo.Iterator.Keys.
|
||||||
|
func IteratorKeys(v *VM) error {
|
||||||
|
iop := v.estack.Pop().Interop()
|
||||||
|
iter := iop.value.(iterator)
|
||||||
|
v.Estack().Push(&Element{value: NewInteropItem(
|
||||||
|
&keysWrapper{iter},
|
||||||
|
)})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IteratorValues handles syscall Neo.Iterator.Values.
|
||||||
|
func IteratorValues(v *VM) error {
|
||||||
|
iop := v.estack.Pop().Interop()
|
||||||
|
iter := iop.value.(iterator)
|
||||||
|
v.Estack().Push(&Element{value: NewInteropItem(
|
||||||
|
&valuesWrapper{iter},
|
||||||
|
)})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
124
pkg/vm/interop_iterators.go
Normal file
124
pkg/vm/interop_iterators.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
type (
|
||||||
|
enumerator interface {
|
||||||
|
Next() bool
|
||||||
|
Value() StackItem
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayWrapper struct {
|
||||||
|
index int
|
||||||
|
value []StackItem
|
||||||
|
}
|
||||||
|
|
||||||
|
concatEnum struct {
|
||||||
|
current enumerator
|
||||||
|
second enumerator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
iterator interface {
|
||||||
|
enumerator
|
||||||
|
Key() StackItem
|
||||||
|
}
|
||||||
|
|
||||||
|
mapWrapper struct {
|
||||||
|
index int
|
||||||
|
keys []interface{}
|
||||||
|
m map[interface{}]StackItem
|
||||||
|
}
|
||||||
|
|
||||||
|
concatIter struct {
|
||||||
|
current iterator
|
||||||
|
second iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
keysWrapper struct {
|
||||||
|
iter iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesWrapper struct {
|
||||||
|
iter iterator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (a *arrayWrapper) Key() StackItem {
|
||||||
|
return makeStackItem(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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *concatIter) Next() bool {
|
||||||
|
if i.current.Next() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
i.current = i.second
|
||||||
|
|
||||||
|
return i.second.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *concatIter) Value() StackItem {
|
||||||
|
return i.current.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *concatIter) Key() StackItem {
|
||||||
|
return i.current.Key()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mapWrapper) Next() bool {
|
||||||
|
if next := m.index + 1; next < len(m.keys) {
|
||||||
|
m.index = next
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mapWrapper) Value() StackItem {
|
||||||
|
return m.m[m.keys[m.index]]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mapWrapper) Key() StackItem {
|
||||||
|
return makeStackItem(m.keys[m.index])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *keysWrapper) Next() bool {
|
||||||
|
return e.iter.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *keysWrapper) Value() StackItem {
|
||||||
|
return e.iter.Key()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *valuesWrapper) Next() bool {
|
||||||
|
return e.iter.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *valuesWrapper) Value() StackItem {
|
||||||
|
return e.iter.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.
|
// Stack represents a Stack backed by a double linked list.
|
||||||
type Stack struct {
|
type Stack struct {
|
||||||
top Element
|
top Element
|
||||||
|
|
|
@ -333,6 +333,160 @@ func TestPushData4Good(t *testing.T) {
|
||||||
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
|
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEnumeratorProg(n int, isIter bool) (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")...)
|
||||||
|
if isIter {
|
||||||
|
prog = append(prog, byte(opcode.DUPFROMALTSTACK))
|
||||||
|
prog = append(prog, getSyscallProg("Neo.Iterator.Key")...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
isIter := typ == "Iterator"
|
||||||
|
prog := getSyscallProg("Neo." + typ + ".Create")
|
||||||
|
prog = append(prog, getEnumeratorProg(2, isIter)...)
|
||||||
|
|
||||||
|
vm := load(prog)
|
||||||
|
arr := []StackItem{
|
||||||
|
NewBigIntegerItem(42),
|
||||||
|
NewByteArrayItem([]byte{3, 2, 1}),
|
||||||
|
}
|
||||||
|
vm.estack.Push(&Element{value: NewArrayItem(arr)})
|
||||||
|
|
||||||
|
runVM(t, vm)
|
||||||
|
if isIter {
|
||||||
|
checkEnumeratorStack(t, vm, []StackItem{
|
||||||
|
makeStackItem(1), arr[1], NewBoolItem(true),
|
||||||
|
makeStackItem(0), arr[0], NewBoolItem(true),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
checkEnumeratorStack(t, vm, []StackItem{
|
||||||
|
arr[1], NewBoolItem(true),
|
||||||
|
arr[0], NewBoolItem(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumeratorCreate(t *testing.T) {
|
||||||
|
testIterableCreate(t, "Enumerator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIteratorCreate(t *testing.T) {
|
||||||
|
testIterableCreate(t, "Iterator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIterableConcat(t *testing.T, typ string) {
|
||||||
|
isIter := typ == "Iterator"
|
||||||
|
prog := getSyscallProg("Neo." + typ + ".Create")
|
||||||
|
prog = append(prog, byte(opcode.SWAP))
|
||||||
|
prog = append(prog, getSyscallProg("Neo."+typ+".Create")...)
|
||||||
|
prog = append(prog, getSyscallProg("Neo."+typ+".Concat")...)
|
||||||
|
prog = append(prog, getEnumeratorProg(3, isIter)...)
|
||||||
|
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)
|
||||||
|
|
||||||
|
if isIter {
|
||||||
|
// Yes, this is how iterators are concatenated in reference VM
|
||||||
|
// https://github.com/neo-project/neo/blob/master-2.x/neo.UnitTests/UT_ConcatenatedIterator.cs#L54
|
||||||
|
checkEnumeratorStack(t, vm, []StackItem{
|
||||||
|
makeStackItem(1), arr[2], NewBoolItem(true),
|
||||||
|
makeStackItem(0), arr[1], NewBoolItem(true),
|
||||||
|
makeStackItem(0), arr[0], NewBoolItem(true),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
checkEnumeratorStack(t, vm, []StackItem{
|
||||||
|
arr[2], NewBoolItem(true),
|
||||||
|
arr[1], NewBoolItem(true),
|
||||||
|
arr[0], NewBoolItem(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumeratorConcat(t *testing.T) {
|
||||||
|
testIterableConcat(t, "Enumerator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIteratorConcat(t *testing.T) {
|
||||||
|
testIterableConcat(t, "Iterator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIteratorKeys(t *testing.T) {
|
||||||
|
prog := getSyscallProg("Neo.Iterator.Create")
|
||||||
|
prog = append(prog, getSyscallProg("Neo.Iterator.Keys")...)
|
||||||
|
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
|
||||||
|
prog = append(prog, getEnumeratorProg(2, false)...)
|
||||||
|
|
||||||
|
v := load(prog)
|
||||||
|
arr := NewArrayItem([]StackItem{
|
||||||
|
NewBoolItem(false),
|
||||||
|
NewBigIntegerItem(42),
|
||||||
|
})
|
||||||
|
v.estack.PushVal(arr)
|
||||||
|
|
||||||
|
runVM(t, v)
|
||||||
|
|
||||||
|
checkEnumeratorStack(t, v, []StackItem{
|
||||||
|
NewBigIntegerItem(1), NewBoolItem(true),
|
||||||
|
NewBigIntegerItem(0), NewBoolItem(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIteratorValues(t *testing.T) {
|
||||||
|
prog := getSyscallProg("Neo.Iterator.Create")
|
||||||
|
prog = append(prog, getSyscallProg("Neo.Iterator.Values")...)
|
||||||
|
prog = append(prog, byte(opcode.TOALTSTACK), byte(opcode.DUPFROMALTSTACK))
|
||||||
|
prog = append(prog, getEnumeratorProg(2, false)...)
|
||||||
|
|
||||||
|
v := load(prog)
|
||||||
|
m := NewMapItem()
|
||||||
|
m.Add(NewBigIntegerItem(1), NewBoolItem(false))
|
||||||
|
m.Add(NewByteArrayItem([]byte{32}), NewByteArrayItem([]byte{7}))
|
||||||
|
v.estack.PushVal(m)
|
||||||
|
|
||||||
|
runVM(t, v)
|
||||||
|
require.Equal(t, 5, v.estack.Len())
|
||||||
|
require.Equal(t, NewBoolItem(false), v.estack.Peek(0).value)
|
||||||
|
|
||||||
|
// Map values can be enumerated in any order.
|
||||||
|
i1, i2 := 1, 3
|
||||||
|
if _, ok := v.estack.Peek(i1).value.(*BoolItem); !ok {
|
||||||
|
i1, i2 = i2, i1
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, NewBoolItem(false), v.estack.Peek(i1).value)
|
||||||
|
require.Equal(t, NewByteArrayItem([]byte{7}), v.estack.Peek(i2).value)
|
||||||
|
|
||||||
|
require.Equal(t, NewBoolItem(true), v.estack.Peek(2).value)
|
||||||
|
require.Equal(t, NewBoolItem(true), v.estack.Peek(4).value)
|
||||||
|
}
|
||||||
|
|
||||||
func getSyscallProg(name string) (prog []byte) {
|
func getSyscallProg(name string) (prog []byte) {
|
||||||
prog = []byte{byte(opcode.SYSCALL)}
|
prog = []byte{byte(opcode.SYSCALL)}
|
||||||
prog = append(prog, byte(len(name)))
|
prog = append(prog, byte(len(name)))
|
||||||
|
|
Loading…
Reference in a new issue