From 721748acfd47d4607fbeb30e26c2c333cdc4cffc Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 5 May 2021 17:48:40 +0300 Subject: [PATCH 1/2] examples: change NFT contract to use storage iterators --- cli/nep11_test.go | 14 ++-- examples/nft-nd/nft.go | 145 ++++++++++++++++------------------------- 2 files changed, 67 insertions(+), 92 deletions(-) diff --git a/cli/nep11_test.go b/cli/nep11_test.go index 035561466..e40d5bc05 100644 --- a/cli/nep11_test.go +++ b/cli/nep11_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "io" "io/ioutil" @@ -233,8 +234,13 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokensOf: good, several tokens tokenID1 := mint(t) e.Run(t, cmdTokensOf...) - e.checkNextLine(t, string(tokenID)) - e.checkNextLine(t, string(tokenID1)) + fst, snd := tokenID, tokenID1 + if bytes.Compare(tokenID, tokenID1) == 1 { + fst, snd = snd, fst + } + + e.checkNextLine(t, string(fst)) + e.checkNextLine(t, string(snd)) // tokens: missing contract hash cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", @@ -245,8 +251,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokens: good, several tokens e.Run(t, cmdTokens...) - e.checkNextLine(t, string(tokenID)) - e.checkNextLine(t, string(tokenID1)) + e.checkNextLine(t, string(fst)) + e.checkNextLine(t, string(snd)) // balance check: several tokens, ok e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) diff --git a/examples/nft-nd/nft.go b/examples/nft-nd/nft.go index aa53bb2c2..eb79c2775 100644 --- a/examples/nft-nd/nft.go +++ b/examples/nft-nd/nft.go @@ -23,9 +23,12 @@ import ( // Prefixes used for contract data storage. const ( totalSupplyPrefix = "s" - accountPrefix = "a" - tokenPrefix = "t" - tokensPrefix = "ts" + // balancePrefix contains map from addresses to balances. + balancePrefix = "b" + // accountPrefix contains map from address + token id to tokens + accountPrefix = "a" + // tokenPrefix contains map from token id to it's owner. + tokenPrefix = "t" ) var ( @@ -63,22 +66,25 @@ func totalSupply(ctx storage.Context) int { return res } -// mkAccountKey creates DB key for account specified by concatenating accountPrefix -// and account address. -func mkAccountKey(holder interop.Hash160) []byte { +// mkAccountPrefix creates DB key-prefix for account tokens specified +// by concatenating accountPrefix and account address. +func mkAccountPrefix(holder interop.Hash160) []byte { res := []byte(accountPrefix) return append(res, holder...) } -// mkStringKey creates DB key for token specified by concatenating tokenPrefix -// and token ID. -func mkTokenKey(token []byte) []byte { - res := []byte(tokenPrefix) - return append(res, token...) +// mkBalanceKey creates DB key for account specified by concatenating balancePrefix +// and account address. +func mkBalanceKey(holder interop.Hash160) []byte { + res := []byte(balancePrefix) + return append(res, holder...) } -func mkTokensKey() []byte { - return []byte(tokensPrefix) +// mkTokenKey creates DB key for token specified by concatenating tokenPrefix +// and token ID. +func mkTokenKey(tokenID []byte) []byte { + res := []byte(tokenPrefix) + return append(res, tokenID...) } // BalanceOf returns the number of tokens owned by specified address. @@ -87,64 +93,48 @@ func BalanceOf(holder interop.Hash160) int { panic("bad owner address") } ctx := storage.GetReadOnlyContext() - tokens := getTokensOf(ctx, holder) - return len(tokens) + return getBalanceOf(ctx, mkBalanceKey(holder)) } -// getTokensOf is an internal implementation of TokensOf, tokens are stored -// as a serialized slice of strings in the DB, so it gets and unwraps them -// (or returns an empty slice). -func getTokensOf(ctx storage.Context, holder interop.Hash160) []string { - var res = []string{} - - key := mkAccountKey(holder) - val := storage.Get(ctx, key) +// getBalanceOf returns balance of the account using database key. +func getBalanceOf(ctx storage.Context, balanceKey []byte) int { + val := storage.Get(ctx, balanceKey) if val != nil { - res = std.Deserialize(val.([]byte)).([]string) + return val.(int) } - return res + return 0 } -// setTokensOf saves current tokens owned by account if there are any, -// otherwise it just drops the appropriate key from the DB. -func setTokensOf(ctx storage.Context, holder interop.Hash160, tokens []string) { - key := mkAccountKey(holder) - if len(tokens) != 0 { - val := std.Serialize(tokens) - storage.Put(ctx, key, val) +// addToBalance adds amount to the account balance. Amount can be negative. +func addToBalance(ctx storage.Context, holder interop.Hash160, amount int) { + key := mkBalanceKey(holder) + old := getBalanceOf(ctx, key) + old += amount + if old > 0 { + storage.Put(ctx, key, old) } else { storage.Delete(ctx, key) } } -// setTokens saves minted token if it is not saved yet. -func setTokens(ctx storage.Context, newToken string) { - key := mkTokensKey() - var tokens = []string{} - val := storage.Get(ctx, key) - if val != nil { - tokens = std.Deserialize(val.([]byte)).([]string) - } - for i := 0; i < len(tokens); i++ { - if util.Equals(tokens[i], newToken) { - return - } - } - tokens = append(tokens, newToken) - val = std.Serialize(tokens) - storage.Put(ctx, key, val) +// addToken adds token to the account. +func addToken(ctx storage.Context, holder interop.Hash160, token []byte) { + key := mkAccountPrefix(holder) + storage.Put(ctx, append(key, token...), token) +} + +// removeToken removes token from the account. +func removeToken(ctx storage.Context, holder interop.Hash160, token []byte) { + key := mkAccountPrefix(holder) + storage.Delete(ctx, append(key, token...)) } // Tokens returns an iterator that contains all of the tokens minted by the contract. func Tokens() iterator.Iterator { ctx := storage.GetReadOnlyContext() - var arr = []string{} - key := mkTokensKey() - val := storage.Get(ctx, key) - if val != nil { - arr = std.Deserialize(val.([]byte)).([]string) - } - return iterator.Create(arr) + key := []byte(tokenPrefix) + iter := storage.Find(ctx, key, storage.RemovePrefix|storage.KeysOnly) + return iter } // TokensOf returns an iterator with all tokens held by specified address. @@ -153,9 +143,9 @@ func TokensOf(holder interop.Hash160) iterator.Iterator { panic("bad owner address") } ctx := storage.GetReadOnlyContext() - tokens := getTokensOf(ctx, holder) - - return iterator.Create(tokens) + key := mkAccountPrefix(holder) + iter := storage.Find(ctx, key, storage.ValuesOnly) + return iter } // getOwnerOf returns current owner of the specified token or panics if token @@ -197,18 +187,11 @@ func Transfer(to interop.Hash160, token []byte, data interface{}) bool { } if string(owner) != string(to) { - toksOwner := getTokensOf(ctx, owner) - toksTo := getTokensOf(ctx, to) + addToBalance(ctx, owner, -1) + removeToken(ctx, owner, token) - var newToksOwner = []string{} - for _, tok := range toksOwner { - if tok != string(token) { - newToksOwner = append(newToksOwner, tok) - } - } - toksTo = append(toksTo, string(token)) - setTokensOf(ctx, owner, newToksOwner) - setTokensOf(ctx, to, toksTo) + addToBalance(ctx, to, 1) + addToken(ctx, to, token) setOwnerOf(ctx, token, to) } postTransfer(owner, to, token, data) @@ -246,14 +229,12 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { tokIn = append(tokIn, std.Serialize(data)...) } - tokenHash := crypto.Sha256(tokIn) + tokenHash := crypto.Ripemd160(tokIn) token := std.Base58Encode(tokenHash) - toksOf := getTokensOf(ctx, from) - toksOf = append(toksOf, token) - setTokensOf(ctx, from, toksOf) + addToken(ctx, from, []byte(token)) setOwnerOf(ctx, []byte(token), from) - setTokens(ctx, token) + addToBalance(ctx, from, 1) total++ storage.Put(ctx, []byte(totalSupplyPrefix), total) @@ -287,20 +268,8 @@ func Update(nef, manifest []byte) { // Properties returns properties of the given NFT. func Properties(id []byte) map[string]string { ctx := storage.GetReadOnlyContext() - var tokens = []string{} - key := mkTokensKey() - val := storage.Get(ctx, key) - if val != nil { - tokens = std.Deserialize(val.([]byte)).([]string) - } - var exists bool - for i := 0; i < len(tokens); i++ { - if util.Equals(tokens[i], id) { - exists = true - break - } - } - if !exists { + owner := storage.Get(ctx, mkTokenKey(id)).(interop.Hash160) + if owner == nil { panic("unknown token") } result := map[string]string{ From 23a4e254363b5a9c31cc9d80d0b3e2c5151a9cba Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 29 Apr 2021 17:19:30 +0300 Subject: [PATCH 2/2] interop: remove `System.Iterator.Create`, fix #1935 There are now only storage iterators. Related #1933. --- pkg/compiler/syscall_test.go | 1 - pkg/core/interop/interopnames/names.go | 2 - pkg/core/interop/iterator/interop.go | 38 ++++++++-- pkg/core/interop/iterator/interop_test.go | 22 +++++- pkg/core/interop_system_test.go | 4 +- pkg/core/interops.go | 1 - pkg/core/native/nonfungible.go | 30 +++++++- pkg/interop/iterator/iterator.go | 8 --- pkg/rpc/response/result/invoke.go | 5 +- pkg/vm/interop.go | 85 ----------------------- pkg/vm/interop_iterators.go | 71 ------------------- pkg/vm/vm_test.go | 62 ----------------- 12 files changed, 83 insertions(+), 246 deletions(-) delete mode 100644 pkg/vm/interop_iterators.go diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index a2b02b385..ff4c011b1 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -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}, diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 6d2da3bea..030629ada 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -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, diff --git a/pkg/core/interop/iterator/interop.go b/pkg/core/interop/iterator/interop.go index f9efa9d09..6a72e8cf9 100644 --- a/pkg/core/interop/iterator/interop.go +++ b/pkg/core/interop/iterator/interop.go @@ -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() } diff --git a/pkg/core/interop/iterator/interop_test.go b/pkg/core/interop/iterator/interop_test.go index fe830fea8..f8d025be2 100644 --- a/pkg/core/interop/iterator/interop_test.go +++ b/pkg/core/interop/iterator/interop_test.go @@ -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 { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index ab3c9da26..f98ecde0f 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -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) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 07f09cb9e..f5f20e6eb 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -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}, diff --git a/pkg/core/native/nonfungible.go b/pkg/core/native/nonfungible.go index c1bf4cc9b..03cd67ef7 100644 --- a/pkg/core/native/nonfungible.go +++ b/pkg/core/native/nonfungible.go @@ -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] +} diff --git a/pkg/interop/iterator/iterator.go b/pkg/interop/iterator/iterator.go index 4169bc0ca..33a78d563 100644 --- a/pkg/interop/iterator/iterator.go +++ b/pkg/interop/iterator/iterator.go @@ -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. diff --git a/pkg/rpc/response/result/invoke.go b/pkg/rpc/response/result/invoke.go index 3f077c7ba..cbd2740f0 100644 --- a/pkg/rpc/response/result/invoke.go +++ b/pkg/rpc/response/result/invoke.go @@ -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]) diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 64f1c0f6f..4885f255e 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -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), - }) -} diff --git a/pkg/vm/interop_iterators.go b/pkg/vm/interop_iterators.go deleted file mode 100644 index 2fc07b5f7..000000000 --- a/pkg/vm/interop_iterators.go +++ /dev/null @@ -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, - }) -} diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 5fcaaa89c..d6d9a4e8a 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -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...)