interop: remove System.Iterator.Create, fix #1935

There are now only storage iterators. Related #1933.
This commit is contained in:
Evgeniy Stratonikov 2021-04-29 17:19:30 +03:00
parent 721748acfd
commit 23a4e25436
12 changed files with 83 additions and 246 deletions

View file

@ -65,7 +65,6 @@ func TestSyscallExecution(t *testing.T) {
"contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false},
"contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false},
"contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false},
"iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false},
"iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false},
"iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false},
"runtime.BurnGas": {interopnames.SystemRuntimeBurnGas, []string{"1"}, true},

View file

@ -13,7 +13,6 @@ const (
SystemContractGetCallFlags = "System.Contract.GetCallFlags"
SystemContractNativeOnPersist = "System.Contract.NativeOnPersist"
SystemContractNativePostPersist = "System.Contract.NativePostPersist"
SystemIteratorCreate = "System.Iterator.Create"
SystemIteratorNext = "System.Iterator.Next"
SystemIteratorValue = "System.Iterator.Value"
SystemRuntimeBurnGas = "System.Runtime.BurnGas"
@ -53,7 +52,6 @@ var names = []string{
SystemContractGetCallFlags,
SystemContractNativeOnPersist,
SystemContractNativePostPersist,
SystemIteratorCreate,
SystemIteratorNext,
SystemIteratorValue,
SystemRuntimeBurnGas,

View file

@ -2,22 +2,48 @@ package iterator
import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Create creates an iterator from array-like or map stack item.
func Create(ic *interop.Context) error {
return vm.IteratorCreate(ic.VM)
type iterator interface {
Next() bool
Value() stackitem.Item
}
// Next advances the iterator, pushes true on success and false otherwise.
func Next(ic *interop.Context) error {
return vm.IteratorNext(ic.VM)
iop := ic.VM.Estack().Pop().Interop()
arr := iop.Value().(iterator)
ic.VM.Estack().PushVal(arr.Next())
return nil
}
// Value returns current iterator value and depends on iterator type:
// For slices the result is just value.
// For maps the result is key-value pair packed in a struct.
func Value(ic *interop.Context) error {
return vm.IteratorValue(ic.VM)
iop := ic.VM.Estack().Pop().Interop()
arr := iop.Value().(iterator)
ic.VM.Estack().PushVal(arr.Value())
return nil
}
// IsIterator returns whether stackitem implements iterator interface.
func IsIterator(item stackitem.Item) bool {
_, ok := item.Value().(iterator)
return ok
}
// Values returns an array of up to `max` iterator values. The second
// return parameter denotes whether iterator is truncated.
func Values(item stackitem.Item, max int) ([]stackitem.Item, bool) {
var result []stackitem.Item
arr := item.Value().(iterator)
for arr.Next() && max > 0 {
result = append(result, arr.Value())
max--
}
return result, arr.Next()
}

View file

@ -6,15 +6,31 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
type testIter struct {
index int
arr []int
}
func (t *testIter) Next() bool {
if t.index < len(t.arr) {
t.index++
}
return t.index < len(t.arr)
}
func (t testIter) Value() stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(int64(t.arr[t.index])))
}
// Iterator is thoroughly tested in VM package, these are smoke tests.
func TestIterator(t *testing.T) {
ic := &interop.Context{VM: vm.New()}
full := []byte{4, 8, 15}
ic.VM.Estack().PushVal(full)
require.NoError(t, Create(ic))
full := []int{4, 8, 15}
ic.VM.Estack().PushVal(stackitem.NewInterop(&testIter{index: -1, arr: full}))
res := ic.VM.Estack().Pop().Item()
for i := range full {

View file

@ -338,8 +338,8 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
invalidStackOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND, opcode.NEWMAP)
emit.Syscall(w.BinWriter, interopnames.SystemIteratorCreate)
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
emit.Opcodes(w.BinWriter, opcode.RET)
callT0Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)

View file

@ -38,7 +38,6 @@ var systemInterops = []interop.Function{
{Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10},
{Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.States},
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: callflag.States},
{Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1},
{Name: interopnames.SystemIteratorNext, Func: iterator.Next, Price: 1 << 15, ParamCount: 1},
{Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1},
{Name: interopnames.SystemRuntimeBurnGas, Func: runtime.BurnGas, Price: 1 << 4, ParamCount: 1},

View file

@ -19,7 +19,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -244,8 +243,8 @@ func (n *nonfungible) tokensOf(ic *interop.Context, args []stackitem.Item) stack
for i := range arr {
arr[i] = stackitem.NewByteArray(s.Tokens[i])
}
iter, _ := vm.NewIterator(stackitem.NewArray(arr))
return iter
iter := newArrayIterator(arr)
return stackitem.NewInterop(iter)
}
func (n *nonfungible) mint(ic *interop.Context, s nftTokenState) {
@ -382,3 +381,28 @@ func (n *nonfungible) transfer(ic *interop.Context, args []stackitem.Item) stack
func makeNFTAccountKey(owner util.Uint160) []byte {
return append([]byte{prefixNFTAccount}, owner.BytesBE()...)
}
type arrayWrapper struct {
index int
value []stackitem.Item
}
func newArrayIterator(arr []stackitem.Item) *arrayWrapper {
return &arrayWrapper{
index: -1,
value: arr,
}
}
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.Item {
return a.value[a.index]
}

View file

@ -11,14 +11,6 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
// structure is similar in function to Neo .net framework's Iterator.
type Iterator struct{}
// Create creates an iterator from the given items (array, struct, map, byte
// array or integer and boolean converted to byte array). A new iterator is set
// to point at element -1, so to access its first element you need to call Next
// first. This function uses `System.Iterator.Create` syscall.
func Create(items interface{}) Iterator {
return neogointernal.Syscall1("System.Iterator.Create", items).(Iterator)
}
// Next advances the iterator returning true if it is was successful (and you
// can use Key or Value) and false otherwise (and there are no more elements in
// this Iterator). This function uses `System.Iterator.Next` syscall.

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -63,8 +64,8 @@ func (r Invoke) MarshalJSON() ([]byte, error) {
data []byte
err error
)
if (r.Stack[i].Type() == stackitem.InteropT) && vm.IsIterator(r.Stack[i]) {
iteratorValues, truncated := vm.IteratorValues(r.Stack[i], r.maxIteratorResultItems)
if (r.Stack[i].Type() == stackitem.InteropT) && iterator.IsIterator(r.Stack[i]) {
iteratorValues, truncated := iterator.Values(r.Stack[i], r.maxIteratorResultItems)
value := make([]json.RawMessage, len(iteratorValues))
for j := range iteratorValues {
value[j], err = stackitem.ToJSONWithTypes(iteratorValues[j])

View file

@ -7,7 +7,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// interopIDFuncPrice adds an ID to the InteropFuncPrice.
@ -23,12 +22,6 @@ var defaultVMInterops = []interopIDFuncPrice{
Func: runtimeLog, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
{ID: interopnames.ToID([]byte(interopnames.SystemRuntimeNotify)),
Func: runtimeNotify, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorCreate)),
Func: IteratorCreate, Price: 1 << 4},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorNext)),
Func: IteratorNext, Price: 1 << 15},
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorValue)),
Func: IteratorValue, Price: 1 << 4},
}
func init() {
@ -70,81 +63,3 @@ func init() {
return defaultVMInterops[i].ID < defaultVMInterops[j].ID
})
}
// IsIterator returns whether stackitem implements iterator interface.
func IsIterator(item stackitem.Item) bool {
_, ok := item.Value().(iterator)
return ok
}
// IteratorNext handles syscall System.Enumerator.Next.
func IteratorNext(v *VM) error {
iop := v.Estack().Pop().Interop()
arr := iop.Value().(iterator)
v.Estack().PushVal(arr.Next())
return nil
}
// IteratorValue handles syscall System.Enumerator.Value.
func IteratorValue(v *VM) error {
iop := v.Estack().Pop().Interop()
arr := iop.Value().(iterator)
v.Estack().Push(&Element{value: arr.Value()})
return nil
}
// IteratorValues returns an array of up to `max` iterator values. The second
// return parameter denotes whether iterator is truncated.
func IteratorValues(item stackitem.Item, max int) ([]stackitem.Item, bool) {
var result []stackitem.Item
arr := item.Value().(iterator)
for arr.Next() && max > 0 {
result = append(result, arr.Value())
max--
}
return result, arr.Next()
}
// NewIterator creates new iterator from the provided stack item.
func NewIterator(item stackitem.Item) (stackitem.Item, error) {
switch t := item.(type) {
case *stackitem.Array, *stackitem.Struct:
return stackitem.NewInterop(&arrayWrapper{
index: -1,
value: t.Value().([]stackitem.Item),
}), nil
case *stackitem.Map:
return NewMapIterator(t), nil
default:
data, err := t.TryBytes()
if err != nil {
return nil, fmt.Errorf("non-iterable type %s", t.Type())
}
return stackitem.NewInterop(&byteArrayWrapper{
index: -1,
value: data,
}), nil
}
}
// IteratorCreate handles syscall System.Iterator.Create.
func IteratorCreate(v *VM) error {
data := v.Estack().Pop().Item()
item, err := NewIterator(data)
if err != nil {
return err
}
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),
})
}

View file

@ -1,71 +0,0 @@
package vm
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
type (
iterator interface {
Next() bool
Value() stackitem.Item
}
arrayWrapper struct {
index int
value []stackitem.Item
}
byteArrayWrapper struct {
index int
value []byte
}
mapWrapper struct {
index int
m []stackitem.MapElement
}
)
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.Item {
return a.value[a.index]
}
func (a *byteArrayWrapper) Next() bool {
if next := a.index + 1; next < len(a.value) {
a.index = next
return true
}
return false
}
func (a *byteArrayWrapper) Value() stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(int64(a.value[a.index])))
}
func (m *mapWrapper) Next() bool {
if next := m.index + 1; next < len(m.m) {
m.index = next
return true
}
return false
}
func (m *mapWrapper) Value() stackitem.Item {
return stackitem.NewStruct([]stackitem.Item{
m.m[m.index].Key,
m.m[m.index].Value,
})
}

View file

@ -468,68 +468,6 @@ func TestPushData4BigN(t *testing.T) {
checkVMFailed(t, vm)
}
func getIteratorProg(n int) (prog []byte) {
prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
for i := 0; i < n; i++ {
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg(interopnames.SystemIteratorNext)...)
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg(interopnames.SystemIteratorValue)...)
}
prog = append(prog, byte(opcode.LDSFLD0))
prog = append(prog, getSyscallProg(interopnames.SystemIteratorNext)...)
return
}
func checkEnumeratorStack(t *testing.T, vm *VM, arr []stackitem.Item) {
require.Equal(t, len(arr)+1, vm.estack.Len())
require.Equal(t, stackitem.NewBool(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, isByteArray bool) {
prog := getSyscallProg(interopnames.SystemIteratorCreate)
prog = append(prog, getIteratorProg(2)...)
vm := load(prog)
arr := []stackitem.Item{
stackitem.NewBigInteger(big.NewInt(42)),
stackitem.NewByteArray([]byte{3, 2, 1}),
}
if isByteArray {
arr[1] = stackitem.Make(7)
vm.estack.PushVal([]byte{42, 7})
} else {
vm.estack.Push(&Element{value: stackitem.NewArray(arr)})
}
runVM(t, vm)
checkEnumeratorStack(t, vm, []stackitem.Item{
arr[1], stackitem.NewBool(true),
arr[0], stackitem.NewBool(true),
})
}
func TestIteratorCreate(t *testing.T) {
t.Run("Array", func(t *testing.T) { testIterableCreate(t, false) })
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, true) })
t.Run("Map", func(f *testing.T) {})
t.Run("Interop", func(t *testing.T) {
v := New()
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
require.Error(t, IteratorCreate(v))
})
}
func getSyscallProg(name string) (prog []byte) {
buf := io.NewBufBinWriter()
emit.Syscall(buf.BinWriter, name)
return buf.Bytes()
}
func getTestCallFlagsFunc(syscall []byte, flags callflag.CallFlag, result interface{}) func(t *testing.T) {
return func(t *testing.T) {
script := append([]byte{byte(opcode.SYSCALL)}, syscall...)