forked from TrueCloudLab/neoneo-go
Merge pull request #1656 from nspcc-dev/vm/iterators
Update iterator API
This commit is contained in:
commit
09ca2cc6e3
22 changed files with 396 additions and 605 deletions
|
@ -6,11 +6,13 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
@ -18,6 +20,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -254,6 +257,41 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("test Storage.Find", func(t *testing.T) {
|
||||||
|
cmd := []string{"neo-go", "contract", "testinvokefunction",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||||
|
h.StringLE(), "testFind"}
|
||||||
|
|
||||||
|
t.Run("keys only", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, strconv.FormatInt(storage.FindKeysOnly, 10))...)
|
||||||
|
res := new(result.Invoke)
|
||||||
|
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
|
||||||
|
require.Equal(t, vm.HaltState.String(), res.State)
|
||||||
|
require.Len(t, res.Stack, 1)
|
||||||
|
require.Equal(t, []stackitem.Item{
|
||||||
|
stackitem.Make("findkey1"),
|
||||||
|
stackitem.Make("findkey2"),
|
||||||
|
}, res.Stack[0].Value())
|
||||||
|
})
|
||||||
|
t.Run("both", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, strconv.FormatInt(storage.FindDefault, 10))...)
|
||||||
|
res := new(result.Invoke)
|
||||||
|
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
|
||||||
|
require.Equal(t, vm.HaltState.String(), res.State)
|
||||||
|
require.Len(t, res.Stack, 1)
|
||||||
|
|
||||||
|
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, arr, 2)
|
||||||
|
require.Equal(t, []stackitem.Item{
|
||||||
|
stackitem.Make("findkey1"), stackitem.Make("value1"),
|
||||||
|
}, arr[0].Value())
|
||||||
|
require.Equal(t, []stackitem.Item{
|
||||||
|
stackitem.Make("findkey2"), stackitem.Make("value2"),
|
||||||
|
}, arr[1].Value())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Update", func(t *testing.T) {
|
t.Run("Update", func(t *testing.T) {
|
||||||
nefName := path.Join(tmpDir, "updated.nef")
|
nefName := path.Join(tmpDir, "updated.nef")
|
||||||
manifestName := path.Join(tmpDir, "updated.manifest.json")
|
manifestName := path.Join(tmpDir, "updated.manifest.json")
|
||||||
|
|
15
cli/testdata/deploy/main.go
vendored
15
cli/testdata/deploy/main.go
vendored
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/cli/testdata/deploy/sub"
|
"github.com/nspcc-dev/neo-go/cli/testdata/deploy/sub"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
)
|
)
|
||||||
|
@ -46,3 +47,17 @@ func GetValue() string {
|
||||||
val2 := storage.Get(ctx, sub.Key)
|
val2 := storage.Get(ctx, sub.Key)
|
||||||
return val1.(string) + "|" + val2.(string)
|
return val1.(string) + "|" + val2.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFind finds items with the specified prefix.
|
||||||
|
func TestFind(f storage.FindFlags) []interface{} {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
storage.Put(ctx, "findkey1", "value1")
|
||||||
|
storage.Put(ctx, "findkey2", "value2")
|
||||||
|
|
||||||
|
var result []interface{}
|
||||||
|
iter := storage.Find(ctx, "findkey", f)
|
||||||
|
for iterator.Next(iter) {
|
||||||
|
result = append(result, iterator.Value(iter))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -8,14 +8,9 @@ import (
|
||||||
|
|
||||||
// NotifyKeysAndValues sends notification with `foo` storage keys and values
|
// NotifyKeysAndValues sends notification with `foo` storage keys and values
|
||||||
func NotifyKeysAndValues() bool {
|
func NotifyKeysAndValues() bool {
|
||||||
iter := storage.Find(storage.GetContext(), []byte("foo"))
|
iter := storage.Find(storage.GetContext(), []byte("foo"), storage.None)
|
||||||
values := iterator.Values(iter)
|
for iterator.Next(iter) {
|
||||||
keys := iterator.Keys(iter)
|
runtime.Notify("found storage key-value pair", iterator.Value(iter))
|
||||||
|
}
|
||||||
runtime.Notify("found storage values", values)
|
|
||||||
// For illustration purposes event is emitted with 'Any' type.
|
|
||||||
var typedKeys interface{} = keys
|
|
||||||
runtime.Notify("found storage keys", typedKeys)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
name: "Iterator example"
|
name: "Iterator example"
|
||||||
supportedstandards: []
|
supportedstandards: []
|
||||||
events:
|
events:
|
||||||
- name: found storage values
|
- name: found storage key-value pair
|
||||||
parameters:
|
parameters:
|
||||||
- name: values
|
- name: value
|
||||||
type: InteropInterface
|
|
||||||
- name: found storage keys
|
|
||||||
parameters:
|
|
||||||
- name: keys
|
|
||||||
type: Any
|
type: Any
|
||||||
|
|
|
@ -32,12 +32,11 @@ func Delete(key []byte) bool {
|
||||||
|
|
||||||
// Find returns an array of key-value pairs with key that matched the passed value
|
// Find returns an array of key-value pairs with key that matched the passed value
|
||||||
func Find(value []byte) []string {
|
func Find(value []byte) []string {
|
||||||
iter := storage.Find(ctx, value)
|
iter := storage.Find(ctx, value, storage.None)
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for iterator.Next(iter) {
|
for iterator.Next(iter) {
|
||||||
val := iterator.Value(iter)
|
val := iterator.Value(iter).([]string)
|
||||||
key := iterator.Key(iter)
|
result = append(result, val[0]+":"+val[1])
|
||||||
result = append(result, key.(string)+":"+val.(string))
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,20 +35,10 @@ var syscalls = map[string]map[string]string{
|
||||||
"RIPEMD160": interopnames.NeoCryptoRIPEMD160,
|
"RIPEMD160": interopnames.NeoCryptoRIPEMD160,
|
||||||
"SHA256": interopnames.NeoCryptoSHA256,
|
"SHA256": interopnames.NeoCryptoSHA256,
|
||||||
},
|
},
|
||||||
"enumerator": {
|
|
||||||
"Concat": interopnames.SystemEnumeratorConcat,
|
|
||||||
"Create": interopnames.SystemEnumeratorCreate,
|
|
||||||
"Next": interopnames.SystemEnumeratorNext,
|
|
||||||
"Value": interopnames.SystemEnumeratorValue,
|
|
||||||
},
|
|
||||||
"iterator": {
|
"iterator": {
|
||||||
"Concat": interopnames.SystemIteratorConcat,
|
|
||||||
"Create": interopnames.SystemIteratorCreate,
|
"Create": interopnames.SystemIteratorCreate,
|
||||||
"Key": interopnames.SystemIteratorKey,
|
"Next": interopnames.SystemIteratorNext,
|
||||||
"Keys": interopnames.SystemIteratorKeys,
|
"Value": interopnames.SystemIteratorValue,
|
||||||
"Next": interopnames.SystemEnumeratorNext,
|
|
||||||
"Value": interopnames.SystemEnumeratorValue,
|
|
||||||
"Values": interopnames.SystemIteratorValues,
|
|
||||||
},
|
},
|
||||||
"json": {
|
"json": {
|
||||||
"Deserialize": interopnames.SystemJSONDeserialize,
|
"Deserialize": interopnames.SystemJSONDeserialize,
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -23,6 +25,16 @@ func TestCallFlags(t *testing.T) {
|
||||||
require.EqualValues(t, contract.NoneFlag, callflag.NoneFlag)
|
require.EqualValues(t, contract.NoneFlag, callflag.NoneFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindFlags(t *testing.T) {
|
||||||
|
require.EqualValues(t, storage.None, istorage.FindDefault)
|
||||||
|
require.EqualValues(t, storage.KeysOnly, istorage.FindKeysOnly)
|
||||||
|
require.EqualValues(t, storage.RemovePrefix, istorage.FindRemovePrefix)
|
||||||
|
require.EqualValues(t, storage.ValuesOnly, istorage.FindValuesOnly)
|
||||||
|
require.EqualValues(t, storage.DeserializeValues, istorage.FindDeserialize)
|
||||||
|
require.EqualValues(t, storage.PickField0, istorage.FindPick0)
|
||||||
|
require.EqualValues(t, storage.PickField1, istorage.FindPick1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestStoragePutGet(t *testing.T) {
|
func TestStoragePutGet(t *testing.T) {
|
||||||
src := `
|
src := `
|
||||||
package foo
|
package foo
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package enumerator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Concat concatenates 2 enumerators into a single one.
|
|
||||||
func Concat(ic *interop.Context) error {
|
|
||||||
return vm.EnumeratorConcat(ic.VM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates an enumerator from an array-like or bytearray-like stack item.
|
|
||||||
func Create(ic *interop.Context) error {
|
|
||||||
return vm.EnumeratorCreate(ic.VM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next advances the enumerator, pushes true if is it was successful
|
|
||||||
// and false otherwise.
|
|
||||||
func Next(ic *interop.Context) error {
|
|
||||||
return vm.EnumeratorNext(ic.VM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the current value of the enumerator.
|
|
||||||
func Value(ic *interop.Context) error {
|
|
||||||
return vm.EnumeratorValue(ic.VM)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package enumerator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enumerator is thoroughly tested in VM package, these are smoke tests.
|
|
||||||
func TestEnumerator(t *testing.T) {
|
|
||||||
ic := &interop.Context{VM: vm.New()}
|
|
||||||
full := []byte{4, 8, 15}
|
|
||||||
ic.VM.Estack().PushVal(full[2:])
|
|
||||||
require.NoError(t, Create(ic))
|
|
||||||
ic.VM.Estack().PushVal(full[:2])
|
|
||||||
require.NoError(t, Create(ic))
|
|
||||||
require.NoError(t, Concat(ic))
|
|
||||||
|
|
||||||
res := ic.VM.Estack().Pop().Item()
|
|
||||||
for i := range full {
|
|
||||||
ic.VM.Estack().PushVal(res)
|
|
||||||
require.NoError(t, Next(ic))
|
|
||||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
|
||||||
ic.VM.Estack().PushVal(res)
|
|
||||||
require.NoError(t, Value(ic))
|
|
||||||
require.Equal(t, big.NewInt(int64(full[i])), ic.VM.Estack().Pop().BigInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
|
||||||
require.NoError(t, Next(ic))
|
|
||||||
require.False(t, ic.VM.Estack().Pop().Bool())
|
|
||||||
}
|
|
|
@ -26,15 +26,9 @@ const (
|
||||||
SystemContractGetCallFlags = "System.Contract.GetCallFlags"
|
SystemContractGetCallFlags = "System.Contract.GetCallFlags"
|
||||||
SystemContractNativeOnPersist = "System.Contract.NativeOnPersist"
|
SystemContractNativeOnPersist = "System.Contract.NativeOnPersist"
|
||||||
SystemContractNativePostPersist = "System.Contract.NativePostPersist"
|
SystemContractNativePostPersist = "System.Contract.NativePostPersist"
|
||||||
SystemEnumeratorConcat = "System.Enumerator.Concat"
|
|
||||||
SystemEnumeratorCreate = "System.Enumerator.Create"
|
|
||||||
SystemEnumeratorNext = "System.Enumerator.Next"
|
|
||||||
SystemEnumeratorValue = "System.Enumerator.Value"
|
|
||||||
SystemIteratorConcat = "System.Iterator.Concat"
|
|
||||||
SystemIteratorCreate = "System.Iterator.Create"
|
SystemIteratorCreate = "System.Iterator.Create"
|
||||||
SystemIteratorKey = "System.Iterator.Key"
|
SystemIteratorNext = "System.Iterator.Next"
|
||||||
SystemIteratorKeys = "System.Iterator.Keys"
|
SystemIteratorValue = "System.Iterator.Value"
|
||||||
SystemIteratorValues = "System.Iterator.Values"
|
|
||||||
SystemJSONDeserialize = "System.Json.Deserialize"
|
SystemJSONDeserialize = "System.Json.Deserialize"
|
||||||
SystemJSONSerialize = "System.Json.Serialize"
|
SystemJSONSerialize = "System.Json.Serialize"
|
||||||
SystemRuntimeCheckWitness = "System.Runtime.CheckWitness"
|
SystemRuntimeCheckWitness = "System.Runtime.CheckWitness"
|
||||||
|
@ -91,15 +85,9 @@ var names = []string{
|
||||||
SystemContractGetCallFlags,
|
SystemContractGetCallFlags,
|
||||||
SystemContractNativeOnPersist,
|
SystemContractNativeOnPersist,
|
||||||
SystemContractNativePostPersist,
|
SystemContractNativePostPersist,
|
||||||
SystemEnumeratorConcat,
|
|
||||||
SystemEnumeratorCreate,
|
|
||||||
SystemEnumeratorNext,
|
|
||||||
SystemEnumeratorValue,
|
|
||||||
SystemIteratorConcat,
|
|
||||||
SystemIteratorCreate,
|
SystemIteratorCreate,
|
||||||
SystemIteratorKey,
|
SystemIteratorNext,
|
||||||
SystemIteratorKeys,
|
SystemIteratorValue,
|
||||||
SystemIteratorValues,
|
|
||||||
SystemJSONDeserialize,
|
SystemJSONDeserialize,
|
||||||
SystemJSONSerialize,
|
SystemJSONSerialize,
|
||||||
SystemRuntimeCheckWitness,
|
SystemRuntimeCheckWitness,
|
||||||
|
|
|
@ -5,27 +5,19 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Concat concatenates 2 iterators into a single one.
|
|
||||||
func Concat(ic *interop.Context) error {
|
|
||||||
return vm.IteratorConcat(ic.VM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates an iterator from array-like or map stack item.
|
// Create creates an iterator from array-like or map stack item.
|
||||||
func Create(ic *interop.Context) error {
|
func Create(ic *interop.Context) error {
|
||||||
return vm.IteratorCreate(ic.VM)
|
return vm.IteratorCreate(ic.VM)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns current iterator key.
|
// Next advances the iterator, pushes true on success and false otherwise.
|
||||||
func Key(ic *interop.Context) error {
|
func Next(ic *interop.Context) error {
|
||||||
return vm.IteratorKey(ic.VM)
|
return vm.IteratorNext(ic.VM)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys returns keys of the iterator.
|
// Value returns current iterator value and depends on iterator type:
|
||||||
func Keys(ic *interop.Context) error {
|
// For slices the result is just value.
|
||||||
return vm.IteratorKeys(ic.VM)
|
// For maps the result is key-value pair packed in a struct.
|
||||||
}
|
func Value(ic *interop.Context) error {
|
||||||
|
return vm.IteratorValue(ic.VM)
|
||||||
// Values returns values of the iterator.
|
|
||||||
func Values(ic *interop.Context) error {
|
|
||||||
return vm.IteratorValues(ic.VM)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,36 +13,23 @@ import (
|
||||||
func TestIterator(t *testing.T) {
|
func TestIterator(t *testing.T) {
|
||||||
ic := &interop.Context{VM: vm.New()}
|
ic := &interop.Context{VM: vm.New()}
|
||||||
full := []byte{4, 8, 15}
|
full := []byte{4, 8, 15}
|
||||||
ic.VM.Estack().PushVal(full[2:])
|
ic.VM.Estack().PushVal(full)
|
||||||
require.NoError(t, Create(ic))
|
require.NoError(t, Create(ic))
|
||||||
ic.VM.Estack().PushVal(full[:2])
|
|
||||||
require.NoError(t, Create(ic))
|
|
||||||
require.NoError(t, Concat(ic))
|
|
||||||
|
|
||||||
res := ic.VM.Estack().Pop().Item()
|
res := ic.VM.Estack().Pop().Item()
|
||||||
|
for i := range full {
|
||||||
ic.VM.Estack().PushVal(res)
|
ic.VM.Estack().PushVal(res)
|
||||||
require.NoError(t, vm.EnumeratorNext(ic.VM))
|
require.NoError(t, Next(ic))
|
||||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
require.True(t, ic.VM.Estack().Pop().Bool())
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
ic.VM.Estack().PushVal(res)
|
||||||
require.NoError(t, Key(ic))
|
require.NoError(t, Value(ic))
|
||||||
require.Equal(t, big.NewInt(0), ic.VM.Estack().Pop().BigInt())
|
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
value := ic.VM.Estack().Pop().Item().Value()
|
||||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
require.Equal(t, big.NewInt(int64(full[i])), value)
|
||||||
require.Equal(t, big.NewInt(int64(full[0])), ic.VM.Estack().Pop().BigInt())
|
}
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
ic.VM.Estack().PushVal(res)
|
||||||
require.NoError(t, vm.EnumeratorNext(ic.VM))
|
require.NoError(t, Next(ic))
|
||||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
require.False(t, false, ic.VM.Estack().Pop().Bool())
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
|
||||||
require.NoError(t, Keys(ic))
|
|
||||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
|
||||||
require.Equal(t, big.NewInt(1), ic.VM.Estack().Pop().BigInt())
|
|
||||||
|
|
||||||
ic.VM.Estack().PushVal(res)
|
|
||||||
require.NoError(t, Values(ic))
|
|
||||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
|
||||||
require.Equal(t, big.NewInt(int64(full[1])), ic.VM.Estack().Pop().BigInt())
|
|
||||||
}
|
}
|
||||||
|
|
75
pkg/core/interop/storage/find.go
Normal file
75
pkg/core/interop/storage/find.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
|
||||||
|
// Storage iterator options.
|
||||||
|
const (
|
||||||
|
FindDefault = 0
|
||||||
|
FindKeysOnly = 1 << 0
|
||||||
|
FindRemovePrefix = 1 << 1
|
||||||
|
FindValuesOnly = 1 << 2
|
||||||
|
FindDeserialize = 1 << 3
|
||||||
|
FindPick0 = 1 << 4
|
||||||
|
FindPick1 = 1 << 5
|
||||||
|
|
||||||
|
FindAll = FindDefault | FindKeysOnly | FindRemovePrefix | FindValuesOnly |
|
||||||
|
FindDeserialize | FindPick0 | FindPick1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Iterator is an iterator state representation.
|
||||||
|
type Iterator struct {
|
||||||
|
m []stackitem.MapElement
|
||||||
|
opts int64
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator creates a new Iterator with given options for a given map.
|
||||||
|
func NewIterator(m *stackitem.Map, opts int64) *Iterator {
|
||||||
|
return &Iterator{
|
||||||
|
m: m.Value().([]stackitem.MapElement),
|
||||||
|
opts: opts,
|
||||||
|
index: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances the iterator and returns true if Value can be called at the
|
||||||
|
// current position.
|
||||||
|
func (s *Iterator) Next() bool {
|
||||||
|
if s.index < len(s.m) {
|
||||||
|
s.index++
|
||||||
|
}
|
||||||
|
return s.index < len(s.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns current iterators value (exact type depends on options this
|
||||||
|
// iterator was created with).
|
||||||
|
func (s *Iterator) Value() stackitem.Item {
|
||||||
|
key := s.m[s.index].Key.Value().([]byte)
|
||||||
|
if s.opts&FindRemovePrefix != 0 {
|
||||||
|
key = key[1:]
|
||||||
|
}
|
||||||
|
if s.opts&FindKeysOnly != 0 {
|
||||||
|
return stackitem.NewByteArray(key)
|
||||||
|
}
|
||||||
|
value := s.m[s.index].Value
|
||||||
|
if s.opts&FindDeserialize != 0 {
|
||||||
|
bs := s.m[s.index].Value.Value().([]byte)
|
||||||
|
var err error
|
||||||
|
value, err = stackitem.DeserializeItem(bs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.opts&FindPick0 != 0 {
|
||||||
|
value = value.Value().([]stackitem.Item)[0]
|
||||||
|
} else if s.opts&FindPick1 != 0 {
|
||||||
|
value = value.Value().([]stackitem.Item)[1]
|
||||||
|
}
|
||||||
|
if s.opts&FindValuesOnly != 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(key),
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
|
@ -7,11 +7,14 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errGasLimitExceeded = errors.New("gas limit exceeded")
|
var (
|
||||||
|
errGasLimitExceeded = errors.New("gas limit exceeded")
|
||||||
|
errFindInvalidOptions = errors.New("invalid Find options")
|
||||||
|
)
|
||||||
|
|
||||||
// storageFind finds stored key-value pair.
|
// storageFind finds stored key-value pair.
|
||||||
func storageFind(ic *interop.Context) error {
|
func storageFind(ic *interop.Context) error {
|
||||||
|
@ -21,6 +24,24 @@ func storageFind(ic *interop.Context) error {
|
||||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
}
|
}
|
||||||
prefix := ic.VM.Estack().Pop().Bytes()
|
prefix := ic.VM.Estack().Pop().Bytes()
|
||||||
|
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
||||||
|
if opts&^storage.FindAll != 0 {
|
||||||
|
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&storage.FindKeysOnly != 0 &&
|
||||||
|
opts&(storage.FindDeserialize|storage.FindPick0|storage.FindPick1) != 0 {
|
||||||
|
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&storage.FindValuesOnly != 0 &&
|
||||||
|
opts&(storage.FindKeysOnly|storage.FindRemovePrefix) != 0 {
|
||||||
|
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&storage.FindPick0 != 0 && opts&storage.FindPick1 != 0 {
|
||||||
|
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&storage.FindDeserialize == 0 && (opts&storage.FindPick0 != 0 || opts&storage.FindPick1 != 0) {
|
||||||
|
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
||||||
|
}
|
||||||
siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix)
|
siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -35,8 +56,8 @@ func storageFind(ic *interop.Context) error {
|
||||||
filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1
|
filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1
|
||||||
})
|
})
|
||||||
|
|
||||||
item := vm.NewMapIterator(filteredMap)
|
item := storage.NewIterator(filteredMap, opts)
|
||||||
ic.VM.Estack().PushVal(item)
|
ic.VM.Estack().PushVal(stackitem.NewInterop(item))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||||
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -37,7 +38,20 @@ func TestStorageFind(t *testing.T) {
|
||||||
v, contractState, context, chain := createVMAndContractState(t)
|
v, contractState, context, chain := createVMAndContractState(t)
|
||||||
defer chain.Close()
|
defer chain.Close()
|
||||||
|
|
||||||
skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01}}
|
arr := []stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(42)),
|
||||||
|
stackitem.NewByteArray([]byte("second")),
|
||||||
|
stackitem.Null{},
|
||||||
|
}
|
||||||
|
rawArr, err := stackitem.SerializeItem(stackitem.NewArray(arr))
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawArr0, err := stackitem.SerializeItem(stackitem.NewArray(arr[:0]))
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawArr1, err := stackitem.SerializeItem(stackitem.NewArray(arr[:1]))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01},
|
||||||
|
{0x04, 0x00}, {0x05, 0x00}, {0x06}, {0x07}, {0x08}}
|
||||||
items := []*state.StorageItem{
|
items := []*state.StorageItem{
|
||||||
{
|
{
|
||||||
Value: []byte{0x01, 0x02, 0x03, 0x04},
|
Value: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
@ -48,6 +62,21 @@ func TestStorageFind(t *testing.T) {
|
||||||
{
|
{
|
||||||
Value: []byte{0x03, 0x04, 0x05, 0x06},
|
Value: []byte{0x03, 0x04, 0x05, 0x06},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Value: []byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: []byte{0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: rawArr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: rawArr0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: rawArr1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
||||||
|
@ -59,56 +88,124 @@ func TestStorageFind(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("normal invocation", func(t *testing.T) {
|
testFind := func(t *testing.T, prefix byte, opts int64, expected []stackitem.Item) {
|
||||||
v.Estack().PushVal([]byte{0x01})
|
v.Estack().PushVal(opts)
|
||||||
|
v.Estack().PushVal([]byte{prefix})
|
||||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||||
|
|
||||||
err := storageFind(context)
|
err := storageFind(context)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var iter *stackitem.Interop
|
var iter *stackitem.Interop
|
||||||
require.NotPanics(t, func() { iter = v.Estack().Top().Interop() })
|
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||||
|
|
||||||
require.NoError(t, enumerator.Next(context))
|
for i := range expected { // sorted indices with mathing prefix
|
||||||
|
v.Estack().PushVal(iter)
|
||||||
|
require.NoError(t, iterator.Next(context))
|
||||||
require.True(t, v.Estack().Pop().Bool())
|
require.True(t, v.Estack().Pop().Bool())
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
v.Estack().PushVal(iter)
|
||||||
require.NoError(t, iterator.Key(context))
|
if expected[i] == nil {
|
||||||
require.Equal(t, []byte{0x01, 0x01}, v.Estack().Pop().Bytes())
|
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, iterator.Value(context))
|
||||||
|
require.Equal(t, expected[i], v.Estack().Pop().Item())
|
||||||
|
}
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
v.Estack().PushVal(iter)
|
||||||
require.NoError(t, enumerator.Value(context))
|
require.NoError(t, iterator.Next(context))
|
||||||
require.Equal(t, []byte{0x03, 0x04, 0x05, 0x06}, v.Estack().Pop().Bytes())
|
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
|
||||||
require.NoError(t, enumerator.Next(context))
|
|
||||||
require.True(t, v.Estack().Pop().Bool())
|
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
|
||||||
require.NoError(t, iterator.Key(context))
|
|
||||||
require.Equal(t, []byte{0x01, 0x02}, v.Estack().Pop().Bytes())
|
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
|
||||||
require.NoError(t, enumerator.Value(context))
|
|
||||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, v.Estack().Pop().Bytes())
|
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
|
||||||
require.NoError(t, enumerator.Next(context))
|
|
||||||
require.False(t, v.Estack().Pop().Bool())
|
require.False(t, v.Estack().Pop().Bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("normal invocation", func(t *testing.T) {
|
||||||
|
testFind(t, 0x01, istorage.FindDefault, []stackitem.Item{
|
||||||
|
stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2]),
|
||||||
|
stackitem.NewByteArray(items[2].Value),
|
||||||
|
}),
|
||||||
|
stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[0]),
|
||||||
|
stackitem.NewByteArray(items[0].Value),
|
||||||
|
}),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("normal invocation, empty result", func(t *testing.T) {
|
t.Run("keys only", func(t *testing.T) {
|
||||||
v.Estack().PushVal([]byte{0x03})
|
testFind(t, 0x01, istorage.FindKeysOnly, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2]),
|
||||||
|
stackitem.NewByteArray(skeys[0]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("remove prefix", func(t *testing.T) {
|
||||||
|
testFind(t, 0x01, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2][1:]),
|
||||||
|
stackitem.NewByteArray(skeys[0][1:]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("values only", func(t *testing.T) {
|
||||||
|
testFind(t, 0x01, istorage.FindValuesOnly, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(items[2].Value),
|
||||||
|
stackitem.NewByteArray(items[0].Value),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("deserialize values", func(t *testing.T) {
|
||||||
|
testFind(t, 0x04, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(items[3].Value[2:]),
|
||||||
|
})
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
v.Estack().PushVal(istorage.FindDeserialize)
|
||||||
|
v.Estack().PushVal([]byte{0x05})
|
||||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||||
|
|
||||||
err := storageFind(context)
|
err := storageFind(context)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, enumerator.Next(context))
|
var iter *stackitem.Interop
|
||||||
require.False(t, v.Estack().Pop().Bool())
|
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||||
|
|
||||||
|
v.Estack().PushVal(iter)
|
||||||
|
require.NoError(t, iterator.Next(context))
|
||||||
|
|
||||||
|
v.Estack().PushVal(iter)
|
||||||
|
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("PickN", func(t *testing.T) {
|
||||||
|
testFind(t, 0x06, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
|
||||||
|
testFind(t, 0x06, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
|
||||||
|
// Array with 0 elements.
|
||||||
|
testFind(t, 0x07, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
|
// Array with 1 element.
|
||||||
|
testFind(t, 0x08, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
|
// Not an array, but serialized ByteArray.
|
||||||
|
testFind(t, 0x04, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("normal invocation, empty result", func(t *testing.T) {
|
||||||
|
testFind(t, 0x03, istorage.FindDefault, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid options", func(t *testing.T) {
|
||||||
|
invalid := []int64{
|
||||||
|
istorage.FindKeysOnly | istorage.FindValuesOnly,
|
||||||
|
^istorage.FindAll,
|
||||||
|
istorage.FindKeysOnly | istorage.FindDeserialize,
|
||||||
|
istorage.FindPick0,
|
||||||
|
istorage.FindPick0 | istorage.FindPick1 | istorage.FindDeserialize,
|
||||||
|
istorage.FindPick0 | istorage.FindPick1,
|
||||||
|
}
|
||||||
|
for _, opts := range invalid {
|
||||||
|
v.Estack().PushVal(opts)
|
||||||
|
v.Estack().PushVal([]byte{0x01})
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||||
|
require.Error(t, storageFind(context))
|
||||||
|
}
|
||||||
|
})
|
||||||
t.Run("invalid type for StorageContext", func(t *testing.T) {
|
t.Run("invalid type for StorageContext", func(t *testing.T) {
|
||||||
|
v.Estack().PushVal(istorage.FindDefault)
|
||||||
v.Estack().PushVal([]byte{0x01})
|
v.Estack().PushVal([]byte{0x01})
|
||||||
v.Estack().PushVal(stackitem.NewInterop(nil))
|
v.Estack().PushVal(stackitem.NewInterop(nil))
|
||||||
|
|
||||||
|
@ -118,11 +215,12 @@ func TestStorageFind(t *testing.T) {
|
||||||
t.Run("invalid id", func(t *testing.T) {
|
t.Run("invalid id", func(t *testing.T) {
|
||||||
invalidID := id + 1
|
invalidID := id + 1
|
||||||
|
|
||||||
|
v.Estack().PushVal(istorage.FindDefault)
|
||||||
v.Estack().PushVal([]byte{0x01})
|
v.Estack().PushVal([]byte{0x01})
|
||||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
|
||||||
|
|
||||||
require.NoError(t, storageFind(context))
|
require.NoError(t, storageFind(context))
|
||||||
require.NoError(t, enumerator.Next(context))
|
require.NoError(t, iterator.Next(context))
|
||||||
require.False(t, v.Estack().Pop().Bool())
|
require.False(t, v.Estack().Pop().Bool())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/binary"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/binary"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/json"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/json"
|
||||||
|
@ -58,15 +57,9 @@ var systemInterops = []interop.Function{
|
||||||
{Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10},
|
{Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10},
|
||||||
{Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.WriteStates},
|
{Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.WriteStates},
|
||||||
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: callflag.WriteStates},
|
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: callflag.WriteStates},
|
||||||
{Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 1 << 4, ParamCount: 2},
|
|
||||||
{Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 1 << 4, ParamCount: 1},
|
|
||||||
{Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1 << 15, ParamCount: 1},
|
|
||||||
{Name: interopnames.SystemEnumeratorValue, Func: enumerator.Value, Price: 1 << 4, ParamCount: 1},
|
|
||||||
{Name: interopnames.SystemIteratorConcat, Func: iterator.Concat, Price: 1 << 4, ParamCount: 2},
|
|
||||||
{Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1},
|
{Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1},
|
||||||
{Name: interopnames.SystemIteratorKey, Func: iterator.Key, Price: 1 << 4, ParamCount: 1},
|
{Name: interopnames.SystemIteratorNext, Func: iterator.Next, Price: 1 << 15, ParamCount: 1},
|
||||||
{Name: interopnames.SystemIteratorKeys, Func: iterator.Keys, Price: 1 << 4, ParamCount: 1},
|
{Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1},
|
||||||
{Name: interopnames.SystemIteratorValues, Func: iterator.Values, Price: 1 << 4, ParamCount: 1},
|
|
||||||
{Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 1 << 14, ParamCount: 1},
|
{Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 1 << 14, ParamCount: 1},
|
||||||
{Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 1 << 12, ParamCount: 1},
|
{Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 1 << 12, ParamCount: 1},
|
||||||
{Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10,
|
{Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10,
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
Package enumerator provides functions to work with enumerators.
|
|
||||||
*/
|
|
||||||
package enumerator
|
|
||||||
|
|
||||||
// Enumerator represents NEO enumerator type, it's an opaque data structure
|
|
||||||
// that can be used with functions from this package. It's similar to more
|
|
||||||
// widely used Iterator (see `iterator` package), but ranging over arrays
|
|
||||||
// or structures that have values with no explicit keys.
|
|
||||||
type Enumerator struct{}
|
|
||||||
|
|
||||||
// Create creates a new enumerator from the given items (slice, structure, byte
|
|
||||||
// array and integer or boolean converted to byte array). New enumerator points
|
|
||||||
// at index -1 of its items, so the user of it has to advance it first with Next.
|
|
||||||
// This function uses `System.Enumerator.Create` syscall.
|
|
||||||
func Create(items interface{}) Enumerator {
|
|
||||||
return Enumerator{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next moves position of the given enumerator by one and returns a bool that
|
|
||||||
// tells whether there is a new value present in this new position. If it is,
|
|
||||||
// you can use Value to get it, if not then there are no more values in this
|
|
||||||
// enumerator. This function uses `System.Enumerator.Next` syscall.
|
|
||||||
func Next(e Enumerator) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns current enumerator's item value, it's only valid to call it
|
|
||||||
// after Next returning true. This function uses `System.Enumerator.Value` syscall.
|
|
||||||
func Value(e Enumerator) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concat concatenates two given enumerators returning one that will range on
|
|
||||||
// a first and then continue with b. Enumerator positions are not reset for a
|
|
||||||
// and b, so if any of them was already advanced by Next the resulting
|
|
||||||
// Enumerator will point at this new position and never go back to previous
|
|
||||||
// values. This function uses `System.Enumerator.Concat` syscall.
|
|
||||||
func Concat(a, b Enumerator) Enumerator {
|
|
||||||
return Enumerator{}
|
|
||||||
}
|
|
|
@ -3,8 +3,6 @@ Package iterator provides functions to work with Neo iterators.
|
||||||
*/
|
*/
|
||||||
package iterator
|
package iterator
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neo-go/pkg/interop/enumerator"
|
|
||||||
|
|
||||||
// Iterator represents a Neo iterator, it's an opaque data structure that can
|
// Iterator represents a Neo iterator, it's an opaque data structure that can
|
||||||
// be properly created by Create or storage.Find. Unlike enumerators, iterators
|
// be properly created by Create or storage.Find. Unlike enumerators, iterators
|
||||||
// range over key-value pairs, so it's convenient to use them for maps. This
|
// range over key-value pairs, so it's convenient to use them for maps. This
|
||||||
|
@ -19,48 +17,18 @@ func Create(items interface{}) Iterator {
|
||||||
return Iterator{}
|
return Iterator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concat concatenates two given iterators returning one that will range on
|
|
||||||
// a first and then continue with b. Iterator positions are not reset for a
|
|
||||||
// and b, so if any of them was already advanced by Next the resulting
|
|
||||||
// Iterator will point at this new position and never go back to previous
|
|
||||||
// key-value pairs. Concatenated iterators also remain completely independent
|
|
||||||
// in results they return, so if both contain the same key you'll receive this
|
|
||||||
// key twice when iterating. This function uses `System.Iterator.Concat` syscall.
|
|
||||||
func Concat(a, b Iterator) Iterator {
|
|
||||||
return Iterator{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns iterator's key at current position. It's only valid to call after
|
|
||||||
// successful Next call. This function uses `System.Iterator.Key` syscall.
|
|
||||||
func Key(it Iterator) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns Enumerator ranging over keys or the given Iterator. Note that
|
|
||||||
// this Enumerator is actually directly tied to the underlying Iterator, so that
|
|
||||||
// advancing it with Next will actually advance the Iterator too. This function
|
|
||||||
// uses `System.Iterator.Keys` syscall.
|
|
||||||
func Keys(it Iterator) enumerator.Enumerator {
|
|
||||||
return enumerator.Enumerator{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next advances the iterator returning true if it is was successful (and you
|
// 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
|
// can use Key or Value) and false otherwise (and there are no more elements in
|
||||||
// this Iterator). This function uses `System.Enumerator.Next` syscall.
|
// this Iterator). This function uses `System.Iterator.Next` syscall.
|
||||||
func Next(it Iterator) bool {
|
func Next(it Iterator) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns iterator's current value. It's only valid to call after
|
// Value returns iterator's current value. It's only valid to call after
|
||||||
// successful Next call. This function uses `System.Enumerator.Value` syscall.
|
// successful Next call. This function uses `System.Iterator.Value` syscall.
|
||||||
|
// For slices the result is just value.
|
||||||
|
// For maps the result can be casted to a slice of 2 elements: key and value.
|
||||||
|
// For storage iterators refer to `storage.FindFlags` documentation.
|
||||||
func Value(it Iterator) interface{} {
|
func Value(it Iterator) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values returns Enumerator ranging over values or the given Iterator. Note that
|
|
||||||
// this Enumerator is actually directly tied to the underlying Iterator, so that
|
|
||||||
// advancing it with Next will actually advance the Iterator too. This function
|
|
||||||
// uses `System.Iterator.Values` syscall.
|
|
||||||
func Values(it Iterator) enumerator.Enumerator {
|
|
||||||
return enumerator.Enumerator{}
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,27 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
// to Neo .net framework's StorageContext class.
|
// to Neo .net framework's StorageContext class.
|
||||||
type Context struct{}
|
type Context struct{}
|
||||||
|
|
||||||
|
// FindFlags represents parameters to `Find` iterator.
|
||||||
|
type FindFlags byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// None is default option. Iterator values are key-value pairs.
|
||||||
|
None FindFlags = 0
|
||||||
|
// KeysOnly is used for iterating over keys.
|
||||||
|
KeysOnly FindFlags = 1 << 0
|
||||||
|
// RemovePrefix is used for stripping 1-byte prefix from keys.
|
||||||
|
RemovePrefix FindFlags = 1 << 1
|
||||||
|
// ValuesOnly is used for iterating over values.
|
||||||
|
ValuesOnly FindFlags = 1 << 2
|
||||||
|
// DeserializeValues is used for deserializing values on-the-fly.
|
||||||
|
// It can be combined with other options.
|
||||||
|
DeserializeValues FindFlags = 1 << 3
|
||||||
|
// PickField0 is used to get first field in a serialized struct or array.
|
||||||
|
PickField0 FindFlags = 1 << 4
|
||||||
|
// PickField1 is used to get second field in a serialized struct or array.
|
||||||
|
PickField1 FindFlags = 1 << 5
|
||||||
|
)
|
||||||
|
|
||||||
// ConvertContextToReadOnly returns new context from the given one, but with
|
// ConvertContextToReadOnly returns new context from the given one, but with
|
||||||
// writing capability turned off, so that you could only invoke Get and Find
|
// writing capability turned off, so that you could only invoke Get and Find
|
||||||
// using this new Context. If Context is already read-only this function is a
|
// using this new Context. If Context is already read-only this function is a
|
||||||
|
@ -58,4 +79,6 @@ func Delete(ctx Context, key interface{}) {}
|
||||||
// that match the given key (contain it as a prefix). See Put documentation on
|
// that match the given key (contain it as a prefix). See Put documentation on
|
||||||
// possible key types and iterator package documentation on how to use the
|
// possible key types and iterator package documentation on how to use the
|
||||||
// returned value. This function uses `System.Storage.Find` syscall.
|
// returned value. This function uses `System.Storage.Find` syscall.
|
||||||
func Find(ctx Context, key interface{}) iterator.Iterator { return iterator.Iterator{} }
|
func Find(ctx Context, key interface{}, options FindFlags) iterator.Iterator {
|
||||||
|
return iterator.Iterator{}
|
||||||
|
}
|
||||||
|
|
|
@ -27,24 +27,12 @@ var defaultVMInterops = []interopIDFuncPrice{
|
||||||
Func: runtimeLog, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
|
Func: runtimeLog, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemRuntimeNotify)),
|
{ID: interopnames.ToID([]byte(interopnames.SystemRuntimeNotify)),
|
||||||
Func: runtimeNotify, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
|
Func: runtimeNotify, Price: 1 << 15, RequiredFlags: callflag.AllowNotify},
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorCreate)),
|
|
||||||
Func: EnumeratorCreate, Price: 1 << 4},
|
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorNext)),
|
|
||||||
Func: EnumeratorNext, Price: 1 << 15},
|
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorConcat)),
|
|
||||||
Func: EnumeratorConcat, Price: 1 << 4},
|
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemEnumeratorValue)),
|
|
||||||
Func: EnumeratorValue, Price: 1 << 4},
|
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorCreate)),
|
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorCreate)),
|
||||||
Func: IteratorCreate, Price: 1 << 4},
|
Func: IteratorCreate, Price: 1 << 4},
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorConcat)),
|
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorNext)),
|
||||||
Func: IteratorConcat, Price: 1 << 4},
|
Func: IteratorNext, Price: 1 << 15},
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorKey)),
|
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorValue)),
|
||||||
Func: IteratorKey, Price: 1 << 4},
|
Func: IteratorValue, Price: 1 << 4},
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorKeys)),
|
|
||||||
Func: IteratorKeys, Price: 1 << 4},
|
|
||||||
{ID: interopnames.ToID([]byte(interopnames.SystemIteratorValues)),
|
|
||||||
Func: IteratorValues, Price: 1 << 4},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -116,67 +104,24 @@ func init() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnumeratorCreate handles syscall System.Enumerator.Create.
|
// IteratorNext handles syscall System.Enumerator.Next.
|
||||||
func EnumeratorCreate(v *VM) error {
|
func IteratorNext(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()
|
iop := v.Estack().Pop().Interop()
|
||||||
arr := iop.Value().(enumerator)
|
arr := iop.Value().(iterator)
|
||||||
v.Estack().PushVal(arr.Next())
|
v.Estack().PushVal(arr.Next())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnumeratorValue handles syscall System.Enumerator.Value.
|
// IteratorValue handles syscall System.Enumerator.Value.
|
||||||
func EnumeratorValue(v *VM) error {
|
func IteratorValue(v *VM) error {
|
||||||
iop := v.Estack().Pop().Interop()
|
iop := v.Estack().Pop().Interop()
|
||||||
arr := iop.Value().(enumerator)
|
arr := iop.Value().(iterator)
|
||||||
v.Estack().Push(&Element{value: arr.Value()})
|
v.Estack().Push(&Element{value: arr.Value()})
|
||||||
|
|
||||||
return nil
|
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.
|
// IteratorCreate handles syscall System.Iterator.Create.
|
||||||
func IteratorCreate(v *VM) error {
|
func IteratorCreate(v *VM) error {
|
||||||
data := v.Estack().Pop()
|
data := v.Estack().Pop()
|
||||||
|
@ -211,51 +156,3 @@ func NewMapIterator(m *stackitem.Map) *stackitem.Interop {
|
||||||
m: m.Value().([]stackitem.MapElement),
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
enumerator interface {
|
iterator interface {
|
||||||
Next() bool
|
Next() bool
|
||||||
Value() stackitem.Item
|
Value() stackitem.Item
|
||||||
}
|
}
|
||||||
|
@ -22,35 +22,10 @@ type (
|
||||||
value []byte
|
value []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
concatEnum struct {
|
|
||||||
current enumerator
|
|
||||||
second enumerator
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
iterator interface {
|
|
||||||
enumerator
|
|
||||||
Key() stackitem.Item
|
|
||||||
}
|
|
||||||
|
|
||||||
mapWrapper struct {
|
mapWrapper struct {
|
||||||
index int
|
index int
|
||||||
m []stackitem.MapElement
|
m []stackitem.MapElement
|
||||||
}
|
}
|
||||||
|
|
||||||
concatIter struct {
|
|
||||||
current iterator
|
|
||||||
second iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
keysWrapper struct {
|
|
||||||
iter iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
valuesWrapper struct {
|
|
||||||
iter iterator
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *arrayWrapper) Next() bool {
|
func (a *arrayWrapper) Next() bool {
|
||||||
|
@ -66,10 +41,6 @@ func (a *arrayWrapper) Value() stackitem.Item {
|
||||||
return a.value[a.index]
|
return a.value[a.index]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *arrayWrapper) Key() stackitem.Item {
|
|
||||||
return stackitem.Make(a.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *byteArrayWrapper) Next() bool {
|
func (a *byteArrayWrapper) Next() bool {
|
||||||
if next := a.index + 1; next < len(a.value) {
|
if next := a.index + 1; next < len(a.value) {
|
||||||
a.index = next
|
a.index = next
|
||||||
|
@ -83,40 +54,6 @@ func (a *byteArrayWrapper) Value() stackitem.Item {
|
||||||
return stackitem.NewBigInteger(big.NewInt(int64(a.value[a.index])))
|
return stackitem.NewBigInteger(big.NewInt(int64(a.value[a.index])))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *byteArrayWrapper) Key() stackitem.Item {
|
|
||||||
return stackitem.Make(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.Item {
|
|
||||||
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.Item {
|
|
||||||
return i.current.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *concatIter) Key() stackitem.Item {
|
|
||||||
return i.current.Key()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mapWrapper) Next() bool {
|
func (m *mapWrapper) Next() bool {
|
||||||
if next := m.index + 1; next < len(m.m) {
|
if next := m.index + 1; next < len(m.m) {
|
||||||
m.index = next
|
m.index = next
|
||||||
|
@ -127,25 +64,8 @@ func (m *mapWrapper) Next() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mapWrapper) Value() stackitem.Item {
|
func (m *mapWrapper) Value() stackitem.Item {
|
||||||
return m.m[m.index].Value
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
}
|
m.m[m.index].Key,
|
||||||
|
m.m[m.index].Value,
|
||||||
func (m *mapWrapper) Key() stackitem.Item {
|
})
|
||||||
return m.m[m.index].Key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *keysWrapper) Next() bool {
|
|
||||||
return e.iter.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *keysWrapper) Value() stackitem.Item {
|
|
||||||
return e.iter.Key()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *valuesWrapper) Next() bool {
|
|
||||||
return e.iter.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *valuesWrapper) Value() stackitem.Item {
|
|
||||||
return e.iter.Value()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -468,20 +468,16 @@ func TestPushData4BigN(t *testing.T) {
|
||||||
checkVMFailed(t, vm)
|
checkVMFailed(t, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnumeratorProg(n int, isIter bool) (prog []byte) {
|
func getIteratorProg(n int) (prog []byte) {
|
||||||
prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
|
prog = []byte{byte(opcode.INITSSLOT), 1, byte(opcode.STSFLD0)}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
prog = append(prog, byte(opcode.LDSFLD0))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemEnumeratorNext)...)
|
prog = append(prog, getSyscallProg(interopnames.SystemIteratorNext)...)
|
||||||
prog = append(prog, byte(opcode.LDSFLD0))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemEnumeratorValue)...)
|
prog = append(prog, getSyscallProg(interopnames.SystemIteratorValue)...)
|
||||||
if isIter {
|
|
||||||
prog = append(prog, byte(opcode.LDSFLD0))
|
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemIteratorKey)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
prog = append(prog, byte(opcode.LDSFLD0))
|
prog = append(prog, byte(opcode.LDSFLD0))
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemEnumeratorNext)...)
|
prog = append(prog, getSyscallProg(interopnames.SystemIteratorNext)...)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -494,10 +490,9 @@ func checkEnumeratorStack(t *testing.T, vm *VM, arr []stackitem.Item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIterableCreate(t *testing.T, typ string, isByteArray bool) {
|
func testIterableCreate(t *testing.T, isByteArray bool) {
|
||||||
isIter := typ == "Iterator"
|
prog := getSyscallProg(interopnames.SystemIteratorCreate)
|
||||||
prog := getSyscallProg("System." + typ + ".Create")
|
prog = append(prog, getIteratorProg(2)...)
|
||||||
prog = append(prog, getEnumeratorProg(2, isIter)...)
|
|
||||||
|
|
||||||
vm := load(prog)
|
vm := load(prog)
|
||||||
arr := []stackitem.Item{
|
arr := []stackitem.Item{
|
||||||
|
@ -512,32 +507,16 @@ func testIterableCreate(t *testing.T, typ string, isByteArray bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runVM(t, vm)
|
runVM(t, vm)
|
||||||
if isIter {
|
|
||||||
checkEnumeratorStack(t, vm, []stackitem.Item{
|
|
||||||
stackitem.Make(1), arr[1], stackitem.NewBool(true),
|
|
||||||
stackitem.Make(0), arr[0], stackitem.NewBool(true),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
checkEnumeratorStack(t, vm, []stackitem.Item{
|
checkEnumeratorStack(t, vm, []stackitem.Item{
|
||||||
arr[1], stackitem.NewBool(true),
|
arr[1], stackitem.NewBool(true),
|
||||||
arr[0], stackitem.NewBool(true),
|
arr[0], stackitem.NewBool(true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnumeratorCreate(t *testing.T) {
|
|
||||||
t.Run("Array", func(t *testing.T) { testIterableCreate(t, "Enumerator", false) })
|
|
||||||
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, "Enumerator", true) })
|
|
||||||
t.Run("Interop", func(t *testing.T) {
|
|
||||||
v := New()
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
|
|
||||||
require.Error(t, EnumeratorCreate(v))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIteratorCreate(t *testing.T) {
|
func TestIteratorCreate(t *testing.T) {
|
||||||
t.Run("Array", func(t *testing.T) { testIterableCreate(t, "Iterator", false) })
|
t.Run("Array", func(t *testing.T) { testIterableCreate(t, false) })
|
||||||
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, "Iterator", true) })
|
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, true) })
|
||||||
|
t.Run("Map", func(f *testing.T) {})
|
||||||
t.Run("Interop", func(t *testing.T) {
|
t.Run("Interop", func(t *testing.T) {
|
||||||
v := New()
|
v := New()
|
||||||
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
|
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
|
||||||
|
@ -545,98 +524,6 @@ func TestIteratorCreate(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIterableConcat(t *testing.T, typ string) {
|
|
||||||
isIter := typ == "Iterator"
|
|
||||||
prog := getSyscallProg("System." + typ + ".Create")
|
|
||||||
prog = append(prog, byte(opcode.SWAP))
|
|
||||||
prog = append(prog, getSyscallProg("System."+typ+".Create")...)
|
|
||||||
prog = append(prog, getSyscallProg("System."+typ+".Concat")...)
|
|
||||||
prog = append(prog, getEnumeratorProg(3, isIter)...)
|
|
||||||
vm := load(prog)
|
|
||||||
|
|
||||||
arr := []stackitem.Item{
|
|
||||||
stackitem.NewBool(false),
|
|
||||||
stackitem.NewBigInteger(big.NewInt(123)),
|
|
||||||
stackitem.NewMap(),
|
|
||||||
}
|
|
||||||
vm.estack.Push(&Element{value: stackitem.NewArray(arr[:1])})
|
|
||||||
vm.estack.Push(&Element{value: stackitem.NewArray(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.Item{
|
|
||||||
stackitem.Make(1), arr[2], stackitem.NewBool(true),
|
|
||||||
stackitem.Make(0), arr[1], stackitem.NewBool(true),
|
|
||||||
stackitem.Make(0), arr[0], stackitem.NewBool(true),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
checkEnumeratorStack(t, vm, []stackitem.Item{
|
|
||||||
arr[2], stackitem.NewBool(true),
|
|
||||||
arr[1], stackitem.NewBool(true),
|
|
||||||
arr[0], stackitem.NewBool(true),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnumeratorConcat(t *testing.T) {
|
|
||||||
testIterableConcat(t, "Enumerator")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIteratorConcat(t *testing.T) {
|
|
||||||
testIterableConcat(t, "Iterator")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIteratorKeys(t *testing.T) {
|
|
||||||
prog := getSyscallProg(interopnames.SystemIteratorCreate)
|
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemIteratorKeys)...)
|
|
||||||
prog = append(prog, getEnumeratorProg(2, false)...)
|
|
||||||
|
|
||||||
v := load(prog)
|
|
||||||
arr := stackitem.NewArray([]stackitem.Item{
|
|
||||||
stackitem.NewBool(false),
|
|
||||||
stackitem.NewBigInteger(big.NewInt(42)),
|
|
||||||
})
|
|
||||||
v.estack.PushVal(arr)
|
|
||||||
|
|
||||||
runVM(t, v)
|
|
||||||
|
|
||||||
checkEnumeratorStack(t, v, []stackitem.Item{
|
|
||||||
stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBool(true),
|
|
||||||
stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBool(true),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIteratorValues(t *testing.T) {
|
|
||||||
prog := getSyscallProg(interopnames.SystemIteratorCreate)
|
|
||||||
prog = append(prog, getSyscallProg(interopnames.SystemIteratorValues)...)
|
|
||||||
prog = append(prog, getEnumeratorProg(2, false)...)
|
|
||||||
|
|
||||||
v := load(prog)
|
|
||||||
m := stackitem.NewMap()
|
|
||||||
m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBool(false))
|
|
||||||
m.Add(stackitem.NewByteArray([]byte{32}), stackitem.NewByteArray([]byte{7}))
|
|
||||||
v.estack.PushVal(m)
|
|
||||||
|
|
||||||
runVM(t, v)
|
|
||||||
require.Equal(t, 5, v.estack.Len())
|
|
||||||
require.Equal(t, stackitem.NewBool(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.(*stackitem.Bool); !ok {
|
|
||||||
i1, i2 = i2, i1
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Equal(t, stackitem.NewBool(false), v.estack.Peek(i1).value)
|
|
||||||
require.Equal(t, stackitem.NewByteArray([]byte{7}), v.estack.Peek(i2).value)
|
|
||||||
|
|
||||||
require.Equal(t, stackitem.NewBool(true), v.estack.Peek(2).value)
|
|
||||||
require.Equal(t, stackitem.NewBool(true), v.estack.Peek(4).value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSyscallProg(name string) (prog []byte) {
|
func getSyscallProg(name string) (prog []byte) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
emit.Syscall(buf.BinWriter, name)
|
emit.Syscall(buf.BinWriter, name)
|
||||||
|
|
Loading…
Reference in a new issue