mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
Merge pull request #1947 from nspcc-dev/iterator-remove
interop: remove `System.Iterator.Create`
This commit is contained in:
commit
3a21d8f44f
14 changed files with 150 additions and 338 deletions
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -233,8 +234,13 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
// tokensOf: good, several tokens
|
// tokensOf: good, several tokens
|
||||||
tokenID1 := mint(t)
|
tokenID1 := mint(t)
|
||||||
e.Run(t, cmdTokensOf...)
|
e.Run(t, cmdTokensOf...)
|
||||||
e.checkNextLine(t, string(tokenID))
|
fst, snd := tokenID, tokenID1
|
||||||
e.checkNextLine(t, string(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
|
// tokens: missing contract hash
|
||||||
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||||
|
@ -245,8 +251,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// tokens: good, several tokens
|
// tokens: good, several tokens
|
||||||
e.Run(t, cmdTokens...)
|
e.Run(t, cmdTokens...)
|
||||||
e.checkNextLine(t, string(tokenID))
|
e.checkNextLine(t, string(fst))
|
||||||
e.checkNextLine(t, string(tokenID1))
|
e.checkNextLine(t, string(snd))
|
||||||
|
|
||||||
// balance check: several tokens, ok
|
// balance check: several tokens, ok
|
||||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
|
|
@ -23,9 +23,12 @@ import (
|
||||||
// Prefixes used for contract data storage.
|
// Prefixes used for contract data storage.
|
||||||
const (
|
const (
|
||||||
totalSupplyPrefix = "s"
|
totalSupplyPrefix = "s"
|
||||||
accountPrefix = "a"
|
// balancePrefix contains map from addresses to balances.
|
||||||
tokenPrefix = "t"
|
balancePrefix = "b"
|
||||||
tokensPrefix = "ts"
|
// accountPrefix contains map from address + token id to tokens
|
||||||
|
accountPrefix = "a"
|
||||||
|
// tokenPrefix contains map from token id to it's owner.
|
||||||
|
tokenPrefix = "t"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -63,22 +66,25 @@ func totalSupply(ctx storage.Context) int {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkAccountKey creates DB key for account specified by concatenating accountPrefix
|
// mkAccountPrefix creates DB key-prefix for account tokens specified
|
||||||
// and account address.
|
// by concatenating accountPrefix and account address.
|
||||||
func mkAccountKey(holder interop.Hash160) []byte {
|
func mkAccountPrefix(holder interop.Hash160) []byte {
|
||||||
res := []byte(accountPrefix)
|
res := []byte(accountPrefix)
|
||||||
return append(res, holder...)
|
return append(res, holder...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkStringKey creates DB key for token specified by concatenating tokenPrefix
|
// mkBalanceKey creates DB key for account specified by concatenating balancePrefix
|
||||||
// and token ID.
|
// and account address.
|
||||||
func mkTokenKey(token []byte) []byte {
|
func mkBalanceKey(holder interop.Hash160) []byte {
|
||||||
res := []byte(tokenPrefix)
|
res := []byte(balancePrefix)
|
||||||
return append(res, token...)
|
return append(res, holder...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkTokensKey() []byte {
|
// mkTokenKey creates DB key for token specified by concatenating tokenPrefix
|
||||||
return []byte(tokensPrefix)
|
// 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.
|
// BalanceOf returns the number of tokens owned by specified address.
|
||||||
|
@ -87,64 +93,48 @@ func BalanceOf(holder interop.Hash160) int {
|
||||||
panic("bad owner address")
|
panic("bad owner address")
|
||||||
}
|
}
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
tokens := getTokensOf(ctx, holder)
|
return getBalanceOf(ctx, mkBalanceKey(holder))
|
||||||
return len(tokens)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTokensOf is an internal implementation of TokensOf, tokens are stored
|
// getBalanceOf returns balance of the account using database key.
|
||||||
// as a serialized slice of strings in the DB, so it gets and unwraps them
|
func getBalanceOf(ctx storage.Context, balanceKey []byte) int {
|
||||||
// (or returns an empty slice).
|
val := storage.Get(ctx, balanceKey)
|
||||||
func getTokensOf(ctx storage.Context, holder interop.Hash160) []string {
|
|
||||||
var res = []string{}
|
|
||||||
|
|
||||||
key := mkAccountKey(holder)
|
|
||||||
val := storage.Get(ctx, key)
|
|
||||||
if val != nil {
|
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,
|
// addToBalance adds amount to the account balance. Amount can be negative.
|
||||||
// otherwise it just drops the appropriate key from the DB.
|
func addToBalance(ctx storage.Context, holder interop.Hash160, amount int) {
|
||||||
func setTokensOf(ctx storage.Context, holder interop.Hash160, tokens []string) {
|
key := mkBalanceKey(holder)
|
||||||
key := mkAccountKey(holder)
|
old := getBalanceOf(ctx, key)
|
||||||
if len(tokens) != 0 {
|
old += amount
|
||||||
val := std.Serialize(tokens)
|
if old > 0 {
|
||||||
storage.Put(ctx, key, val)
|
storage.Put(ctx, key, old)
|
||||||
} else {
|
} else {
|
||||||
storage.Delete(ctx, key)
|
storage.Delete(ctx, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTokens saves minted token if it is not saved yet.
|
// addToken adds token to the account.
|
||||||
func setTokens(ctx storage.Context, newToken string) {
|
func addToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||||
key := mkTokensKey()
|
key := mkAccountPrefix(holder)
|
||||||
var tokens = []string{}
|
storage.Put(ctx, append(key, token...), token)
|
||||||
val := storage.Get(ctx, key)
|
}
|
||||||
if val != nil {
|
|
||||||
tokens = std.Deserialize(val.([]byte)).([]string)
|
// removeToken removes token from the account.
|
||||||
}
|
func removeToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||||
for i := 0; i < len(tokens); i++ {
|
key := mkAccountPrefix(holder)
|
||||||
if util.Equals(tokens[i], newToken) {
|
storage.Delete(ctx, append(key, token...))
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens = append(tokens, newToken)
|
|
||||||
val = std.Serialize(tokens)
|
|
||||||
storage.Put(ctx, key, val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokens returns an iterator that contains all of the tokens minted by the contract.
|
// Tokens returns an iterator that contains all of the tokens minted by the contract.
|
||||||
func Tokens() iterator.Iterator {
|
func Tokens() iterator.Iterator {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
var arr = []string{}
|
key := []byte(tokenPrefix)
|
||||||
key := mkTokensKey()
|
iter := storage.Find(ctx, key, storage.RemovePrefix|storage.KeysOnly)
|
||||||
val := storage.Get(ctx, key)
|
return iter
|
||||||
if val != nil {
|
|
||||||
arr = std.Deserialize(val.([]byte)).([]string)
|
|
||||||
}
|
|
||||||
return iterator.Create(arr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokensOf returns an iterator with all tokens held by specified address.
|
// 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")
|
panic("bad owner address")
|
||||||
}
|
}
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
tokens := getTokensOf(ctx, holder)
|
key := mkAccountPrefix(holder)
|
||||||
|
iter := storage.Find(ctx, key, storage.ValuesOnly)
|
||||||
return iterator.Create(tokens)
|
return iter
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOwnerOf returns current owner of the specified token or panics if token
|
// 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) {
|
if string(owner) != string(to) {
|
||||||
toksOwner := getTokensOf(ctx, owner)
|
addToBalance(ctx, owner, -1)
|
||||||
toksTo := getTokensOf(ctx, to)
|
removeToken(ctx, owner, token)
|
||||||
|
|
||||||
var newToksOwner = []string{}
|
addToBalance(ctx, to, 1)
|
||||||
for _, tok := range toksOwner {
|
addToken(ctx, to, token)
|
||||||
if tok != string(token) {
|
|
||||||
newToksOwner = append(newToksOwner, tok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toksTo = append(toksTo, string(token))
|
|
||||||
setTokensOf(ctx, owner, newToksOwner)
|
|
||||||
setTokensOf(ctx, to, toksTo)
|
|
||||||
setOwnerOf(ctx, token, to)
|
setOwnerOf(ctx, token, to)
|
||||||
}
|
}
|
||||||
postTransfer(owner, to, token, data)
|
postTransfer(owner, to, token, data)
|
||||||
|
@ -246,14 +229,12 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
||||||
tokIn = append(tokIn, std.Serialize(data)...)
|
tokIn = append(tokIn, std.Serialize(data)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenHash := crypto.Sha256(tokIn)
|
tokenHash := crypto.Ripemd160(tokIn)
|
||||||
token := std.Base58Encode(tokenHash)
|
token := std.Base58Encode(tokenHash)
|
||||||
|
|
||||||
toksOf := getTokensOf(ctx, from)
|
addToken(ctx, from, []byte(token))
|
||||||
toksOf = append(toksOf, token)
|
|
||||||
setTokensOf(ctx, from, toksOf)
|
|
||||||
setOwnerOf(ctx, []byte(token), from)
|
setOwnerOf(ctx, []byte(token), from)
|
||||||
setTokens(ctx, token)
|
addToBalance(ctx, from, 1)
|
||||||
|
|
||||||
total++
|
total++
|
||||||
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
||||||
|
@ -287,20 +268,8 @@ func Update(nef, manifest []byte) {
|
||||||
// Properties returns properties of the given NFT.
|
// Properties returns properties of the given NFT.
|
||||||
func Properties(id []byte) map[string]string {
|
func Properties(id []byte) map[string]string {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
var tokens = []string{}
|
owner := storage.Get(ctx, mkTokenKey(id)).(interop.Hash160)
|
||||||
key := mkTokensKey()
|
if owner == nil {
|
||||||
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 {
|
|
||||||
panic("unknown token")
|
panic("unknown token")
|
||||||
}
|
}
|
||||||
result := map[string]string{
|
result := map[string]string{
|
||||||
|
|
|
@ -65,7 +65,6 @@ func TestSyscallExecution(t *testing.T) {
|
||||||
"contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false},
|
"contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false},
|
||||||
"contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false},
|
"contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false},
|
||||||
"contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false},
|
"contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false},
|
||||||
"iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false},
|
|
||||||
"iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false},
|
"iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false},
|
||||||
"iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false},
|
"iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false},
|
||||||
"runtime.BurnGas": {interopnames.SystemRuntimeBurnGas, []string{"1"}, true},
|
"runtime.BurnGas": {interopnames.SystemRuntimeBurnGas, []string{"1"}, true},
|
||||||
|
|
|
@ -13,7 +13,6 @@ 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"
|
||||||
SystemIteratorCreate = "System.Iterator.Create"
|
|
||||||
SystemIteratorNext = "System.Iterator.Next"
|
SystemIteratorNext = "System.Iterator.Next"
|
||||||
SystemIteratorValue = "System.Iterator.Value"
|
SystemIteratorValue = "System.Iterator.Value"
|
||||||
SystemRuntimeBurnGas = "System.Runtime.BurnGas"
|
SystemRuntimeBurnGas = "System.Runtime.BurnGas"
|
||||||
|
@ -53,7 +52,6 @@ var names = []string{
|
||||||
SystemContractGetCallFlags,
|
SystemContractGetCallFlags,
|
||||||
SystemContractNativeOnPersist,
|
SystemContractNativeOnPersist,
|
||||||
SystemContractNativePostPersist,
|
SystemContractNativePostPersist,
|
||||||
SystemIteratorCreate,
|
|
||||||
SystemIteratorNext,
|
SystemIteratorNext,
|
||||||
SystemIteratorValue,
|
SystemIteratorValue,
|
||||||
SystemRuntimeBurnGas,
|
SystemRuntimeBurnGas,
|
||||||
|
|
|
@ -2,22 +2,48 @@ package iterator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create creates an iterator from array-like or map stack item.
|
type iterator interface {
|
||||||
func Create(ic *interop.Context) error {
|
Next() bool
|
||||||
return vm.IteratorCreate(ic.VM)
|
Value() stackitem.Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next advances the iterator, pushes true on success and false otherwise.
|
// Next advances the iterator, pushes true on success and false otherwise.
|
||||||
func Next(ic *interop.Context) error {
|
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:
|
// Value returns current iterator value and depends on iterator type:
|
||||||
// For slices the result is just value.
|
// For slices the result is just value.
|
||||||
// For maps the result is key-value pair packed in a struct.
|
// For maps the result is key-value pair packed in a struct.
|
||||||
func Value(ic *interop.Context) error {
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,31 @@ import (
|
||||||
|
|
||||||
"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/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"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.
|
// Iterator is thoroughly tested in VM package, these are smoke tests.
|
||||||
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 := []int{4, 8, 15}
|
||||||
ic.VM.Estack().PushVal(full)
|
ic.VM.Estack().PushVal(stackitem.NewInterop(&testIter{index: -1, arr: full}))
|
||||||
require.NoError(t, Create(ic))
|
|
||||||
|
|
||||||
res := ic.VM.Estack().Pop().Item()
|
res := ic.VM.Estack().Pop().Item()
|
||||||
for i := range full {
|
for i := range full {
|
||||||
|
|
|
@ -338,8 +338,8 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
emit.Opcodes(w.BinWriter, opcode.DROP)
|
emit.Opcodes(w.BinWriter, opcode.DROP)
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
invalidStackOff := w.Len()
|
invalidStackOff := w.Len()
|
||||||
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND, opcode.NEWMAP)
|
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemIteratorCreate)
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
callT0Off := w.Len()
|
callT0Off := w.Len()
|
||||||
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
|
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
|
||||||
|
|
|
@ -38,7 +38,6 @@ 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.States},
|
{Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.States},
|
||||||
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, 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.SystemIteratorNext, Func: iterator.Next, Price: 1 << 15, ParamCount: 1},
|
||||||
{Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1},
|
{Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1},
|
||||||
{Name: interopnames.SystemRuntimeBurnGas, Func: runtime.BurnGas, Price: 1 << 4, ParamCount: 1},
|
{Name: interopnames.SystemRuntimeBurnGas, Func: runtime.BurnGas, Price: 1 << 4, ParamCount: 1},
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"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/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"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/stackitem"
|
"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 {
|
for i := range arr {
|
||||||
arr[i] = stackitem.NewByteArray(s.Tokens[i])
|
arr[i] = stackitem.NewByteArray(s.Tokens[i])
|
||||||
}
|
}
|
||||||
iter, _ := vm.NewIterator(stackitem.NewArray(arr))
|
iter := newArrayIterator(arr)
|
||||||
return iter
|
return stackitem.NewInterop(iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nonfungible) mint(ic *interop.Context, s nftTokenState) {
|
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 {
|
func makeNFTAccountKey(owner util.Uint160) []byte {
|
||||||
return append([]byte{prefixNFTAccount}, owner.BytesBE()...)
|
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]
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
// structure is similar in function to Neo .net framework's Iterator.
|
||||||
type Iterator struct{}
|
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
|
// 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.Iterator.Next` syscall.
|
// this Iterator). This function uses `System.Iterator.Next` syscall.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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/core/transaction"
|
||||||
"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/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
@ -63,8 +64,8 @@ func (r Invoke) MarshalJSON() ([]byte, error) {
|
||||||
data []byte
|
data []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if (r.Stack[i].Type() == stackitem.InteropT) && vm.IsIterator(r.Stack[i]) {
|
if (r.Stack[i].Type() == stackitem.InteropT) && iterator.IsIterator(r.Stack[i]) {
|
||||||
iteratorValues, truncated := vm.IteratorValues(r.Stack[i], r.maxIteratorResultItems)
|
iteratorValues, truncated := iterator.Values(r.Stack[i], r.maxIteratorResultItems)
|
||||||
value := make([]json.RawMessage, len(iteratorValues))
|
value := make([]json.RawMessage, len(iteratorValues))
|
||||||
for j := range iteratorValues {
|
for j := range iteratorValues {
|
||||||
value[j], err = stackitem.ToJSONWithTypes(iteratorValues[j])
|
value[j], err = stackitem.ToJSONWithTypes(iteratorValues[j])
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
|
|
||||||
"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/smartcontract/callflag"
|
"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.
|
// interopIDFuncPrice adds an ID to the InteropFuncPrice.
|
||||||
|
@ -23,12 +22,6 @@ 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.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() {
|
func init() {
|
||||||
|
@ -70,81 +63,3 @@ func init() {
|
||||||
return defaultVMInterops[i].ID < defaultVMInterops[j].ID
|
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -468,68 +468,6 @@ func TestPushData4BigN(t *testing.T) {
|
||||||
checkVMFailed(t, vm)
|
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) {
|
func getTestCallFlagsFunc(syscall []byte, flags callflag.CallFlag, result interface{}) func(t *testing.T) {
|
||||||
return func(t *testing.T) {
|
return func(t *testing.T) {
|
||||||
script := append([]byte{byte(opcode.SYSCALL)}, syscall...)
|
script := append([]byte{byte(opcode.SYSCALL)}, syscall...)
|
||||||
|
|
Loading…
Reference in a new issue