forked from TrueCloudLab/neoneo-go
core: remove neointerops-related files
And move their content to systeminterops-related files.
This commit is contained in:
parent
4b933f88a7
commit
f824789116
4 changed files with 308 additions and 346 deletions
|
@ -1,66 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errGasLimitExceeded = errors.New("gas limit exceeded")
|
|
||||||
errFindInvalidOptions = errors.New("invalid Find options")
|
|
||||||
)
|
|
||||||
|
|
||||||
// storageFind finds stored key-value pair.
|
|
||||||
func storageFind(ic *interop.Context) error {
|
|
||||||
stcInterface := ic.VM.Estack().Pop().Value()
|
|
||||||
stc, ok := stcInterface.(*StorageContext)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredMap := stackitem.NewMap()
|
|
||||||
for k, v := range siMap {
|
|
||||||
key := append(prefix, []byte(k)...)
|
|
||||||
keycopy := make([]byte, len(key))
|
|
||||||
copy(keycopy, key)
|
|
||||||
filteredMap.Add(stackitem.NewByteArray(keycopy), stackitem.NewByteArray(v))
|
|
||||||
}
|
|
||||||
sort.Slice(filteredMap.Value().([]stackitem.MapElement), func(i, j int) bool {
|
|
||||||
return bytes.Compare(filteredMap.Value().([]stackitem.MapElement)[i].Key.Value().([]byte),
|
|
||||||
filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1
|
|
||||||
})
|
|
||||||
|
|
||||||
item := storage.NewIterator(filteredMap, len(prefix), opts)
|
|
||||||
ic.VM.Estack().PushVal(stackitem.NewInterop(item))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,280 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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/interop"
|
|
||||||
"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/storage"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Missing tests:
|
|
||||||
* TestTxGetWitnesses
|
|
||||||
* TestAccountIsStandard
|
|
||||||
* TestCreateContractStateFromVM
|
|
||||||
* TestContractCreate
|
|
||||||
* TestContractMigrate
|
|
||||||
* TestRuntimeSerialize
|
|
||||||
* TestRuntimeDeserialize
|
|
||||||
*/
|
|
||||||
|
|
||||||
func TestStorageFind(t *testing.T) {
|
|
||||||
v, contractState, context, chain := createVMAndContractState(t)
|
|
||||||
|
|
||||||
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},
|
|
||||||
{0x09, 0x12, 0x34}, {0x09, 0x12, 0x56},
|
|
||||||
}
|
|
||||||
items := []state.StorageItem{
|
|
||||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
|
||||||
[]byte{0x04, 0x03, 0x02, 0x01},
|
|
||||||
[]byte{0x03, 0x04, 0x05, 0x06},
|
|
||||||
[]byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
|
|
||||||
[]byte{0xFF, 0xFF},
|
|
||||||
rawArr,
|
|
||||||
rawArr0,
|
|
||||||
rawArr1,
|
|
||||||
[]byte{111},
|
|
||||||
[]byte{222},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
|
||||||
|
|
||||||
id := contractState.ID
|
|
||||||
|
|
||||||
for i := range skeys {
|
|
||||||
err := context.DAO.PutStorageItem(id, skeys[i], items[i])
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
|
|
||||||
v.Estack().PushVal(opts)
|
|
||||||
v.Estack().PushVal(prefix)
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
|
||||||
|
|
||||||
err := storageFind(context)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var iter *stackitem.Interop
|
|
||||||
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
v.Estack().PushVal(iter)
|
|
||||||
if expected[i] == nil {
|
|
||||||
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)
|
|
||||||
require.NoError(t, iterator.Next(context))
|
|
||||||
require.False(t, v.Estack().Pop().Bool())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("normal invocation", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{0x01}, istorage.FindDefault, []stackitem.Item{
|
|
||||||
stackitem.NewStruct([]stackitem.Item{
|
|
||||||
stackitem.NewByteArray(skeys[2]),
|
|
||||||
stackitem.NewByteArray(items[2]),
|
|
||||||
}),
|
|
||||||
stackitem.NewStruct([]stackitem.Item{
|
|
||||||
stackitem.NewByteArray(skeys[0]),
|
|
||||||
stackitem.NewByteArray(items[0]),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("keys only", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly, []stackitem.Item{
|
|
||||||
stackitem.NewByteArray(skeys[2]),
|
|
||||||
stackitem.NewByteArray(skeys[0]),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("remove prefix", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
|
||||||
stackitem.NewByteArray(skeys[2][1:]),
|
|
||||||
stackitem.NewByteArray(skeys[0][1:]),
|
|
||||||
})
|
|
||||||
testFind(t, []byte{0x09, 0x12}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
|
||||||
stackitem.NewByteArray(skeys[8][2:]),
|
|
||||||
stackitem.NewByteArray(skeys[9][2:]),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("values only", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{0x01}, istorage.FindValuesOnly, []stackitem.Item{
|
|
||||||
stackitem.NewByteArray(items[2]),
|
|
||||||
stackitem.NewByteArray(items[0]),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("deserialize values", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{0x04}, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
|
|
||||||
stackitem.NewByteArray(items[3][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}))
|
|
||||||
err := storageFind(context)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var iter *stackitem.Interop
|
|
||||||
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, []byte{0x06}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
|
|
||||||
testFind(t, []byte{0x06}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
|
|
||||||
// Array with 0 elements.
|
|
||||||
testFind(t, []byte{0x07}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
|
|
||||||
[]stackitem.Item{nil})
|
|
||||||
// Array with 1 element.
|
|
||||||
testFind(t, []byte{0x08}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
|
||||||
[]stackitem.Item{nil})
|
|
||||||
// Not an array, but serialized ByteArray.
|
|
||||||
testFind(t, []byte{0x04}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
|
||||||
[]stackitem.Item{nil})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("normal invocation, empty result", func(t *testing.T) {
|
|
||||||
testFind(t, []byte{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) {
|
|
||||||
v.Estack().PushVal(istorage.FindDefault)
|
|
||||||
v.Estack().PushVal([]byte{0x01})
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(nil))
|
|
||||||
|
|
||||||
require.Error(t, storageFind(context))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("invalid id", func(t *testing.T) {
|
|
||||||
invalidID := id + 1
|
|
||||||
|
|
||||||
v.Estack().PushVal(istorage.FindDefault)
|
|
||||||
v.Estack().PushVal([]byte{0x01})
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
|
|
||||||
|
|
||||||
require.NoError(t, storageFind(context))
|
|
||||||
require.NoError(t, iterator.Next(context))
|
|
||||||
require.False(t, v.Estack().Pop().Bool())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
|
||||||
|
|
||||||
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
context := chain.newInteropContext(trigger.Application,
|
|
||||||
dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader), nil, nil)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
|
||||||
v, block, context, chain := createVMAndBlock(t)
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(block))
|
|
||||||
return v, block, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
|
||||||
block := newDumbBlock()
|
|
||||||
chain := newTestChain(t)
|
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.GetConfig().StateRootInHeader)
|
|
||||||
context := chain.newInteropContext(trigger.Application, d, block, nil)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, block, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
|
||||||
v, tx, context, chain := createVMAndTX(t)
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(tx))
|
|
||||||
return v, tx, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
|
|
||||||
script := []byte("testscript")
|
|
||||||
m := manifest.NewManifest("Test")
|
|
||||||
ne, err := nef.NewFile(script)
|
|
||||||
require.NoError(t, err)
|
|
||||||
contractState := &state.Contract{
|
|
||||||
ContractBase: state.ContractBase{
|
|
||||||
NEF: *ne,
|
|
||||||
Hash: hash.Hash160(script),
|
|
||||||
Manifest: *m,
|
|
||||||
ID: 123,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
chain := newTestChain(t)
|
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader)
|
|
||||||
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, contractState, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
|
||||||
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
|
||||||
tx := transaction.New(script, 0)
|
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}}
|
|
||||||
tx.Scripts = []transaction.Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}}
|
|
||||||
chain := newTestChain(t)
|
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader)
|
|
||||||
context := chain.newInteropContext(trigger.Application, d, nil, tx)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, tx, context, chain
|
|
||||||
}
|
|
|
@ -1,13 +1,16 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"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/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"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"
|
||||||
|
@ -17,6 +20,11 @@ import (
|
||||||
"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")
|
||||||
|
errFindInvalidOptions = errors.New("invalid Find options")
|
||||||
|
)
|
||||||
|
|
||||||
// StorageContext contains storing id and read/write flag, it's used as
|
// StorageContext contains storing id and read/write flag, it's used as
|
||||||
// a context for storage manipulation functions.
|
// a context for storage manipulation functions.
|
||||||
type StorageContext struct {
|
type StorageContext struct {
|
||||||
|
@ -153,6 +161,55 @@ func storageContextAsReadOnly(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// storageFind finds stored key-value pair.
|
||||||
|
func storageFind(ic *interop.Context) error {
|
||||||
|
stcInterface := ic.VM.Estack().Pop().Value()
|
||||||
|
stc, ok := stcInterface.(*StorageContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||||
|
}
|
||||||
|
prefix := ic.VM.Estack().Pop().Bytes()
|
||||||
|
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
||||||
|
if opts&^istorage.FindAll != 0 {
|
||||||
|
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&istorage.FindKeysOnly != 0 &&
|
||||||
|
opts&(istorage.FindDeserialize|istorage.FindPick0|istorage.FindPick1) != 0 {
|
||||||
|
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&istorage.FindValuesOnly != 0 &&
|
||||||
|
opts&(istorage.FindKeysOnly|istorage.FindRemovePrefix) != 0 {
|
||||||
|
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&istorage.FindPick0 != 0 && opts&istorage.FindPick1 != 0 {
|
||||||
|
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
if opts&istorage.FindDeserialize == 0 && (opts&istorage.FindPick0 != 0 || opts&istorage.FindPick1 != 0) {
|
||||||
|
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
||||||
|
}
|
||||||
|
siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredMap := stackitem.NewMap()
|
||||||
|
for k, v := range siMap {
|
||||||
|
key := append(prefix, []byte(k)...)
|
||||||
|
keycopy := make([]byte, len(key))
|
||||||
|
copy(keycopy, key)
|
||||||
|
filteredMap.Add(stackitem.NewByteArray(keycopy), stackitem.NewByteArray(v))
|
||||||
|
}
|
||||||
|
sort.Slice(filteredMap.Value().([]stackitem.MapElement), func(i, j int) bool {
|
||||||
|
return bytes.Compare(filteredMap.Value().([]stackitem.MapElement)[i].Key.Value().([]byte),
|
||||||
|
filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1
|
||||||
|
})
|
||||||
|
|
||||||
|
item := istorage.NewIterator(filteredMap, len(prefix), opts)
|
||||||
|
ic.VM.Estack().PushVal(stackitem.NewInterop(item))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// contractCreateMultisigAccount calculates multisig contract scripthash for a
|
// contractCreateMultisigAccount calculates multisig contract scripthash for a
|
||||||
// given m and a set of public keys.
|
// given m and a set of public keys.
|
||||||
func contractCreateMultisigAccount(ic *interop.Context) error {
|
func contractCreateMultisigAccount(ic *interop.Context) error {
|
||||||
|
|
|
@ -7,10 +7,14 @@ import (
|
||||||
"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/core/block"
|
||||||
|
"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/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"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/runtime"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||||
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"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"
|
||||||
|
@ -22,6 +26,7 @@ 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/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"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/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
@ -254,6 +259,252 @@ func TestStorageDelete(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStorageFind(t *testing.T) {
|
||||||
|
v, contractState, context, chain := createVMAndContractState(t)
|
||||||
|
|
||||||
|
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},
|
||||||
|
{0x09, 0x12, 0x34}, {0x09, 0x12, 0x56},
|
||||||
|
}
|
||||||
|
items := []state.StorageItem{
|
||||||
|
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
[]byte{0x04, 0x03, 0x02, 0x01},
|
||||||
|
[]byte{0x03, 0x04, 0x05, 0x06},
|
||||||
|
[]byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
|
||||||
|
[]byte{0xFF, 0xFF},
|
||||||
|
rawArr,
|
||||||
|
rawArr0,
|
||||||
|
rawArr1,
|
||||||
|
[]byte{111},
|
||||||
|
[]byte{222},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
||||||
|
|
||||||
|
id := contractState.ID
|
||||||
|
|
||||||
|
for i := range skeys {
|
||||||
|
err := context.DAO.PutStorageItem(id, skeys[i], items[i])
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
|
||||||
|
v.Estack().PushVal(opts)
|
||||||
|
v.Estack().PushVal(prefix)
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||||
|
|
||||||
|
err := storageFind(context)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var iter *stackitem.Interop
|
||||||
|
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
v.Estack().PushVal(iter)
|
||||||
|
if expected[i] == nil {
|
||||||
|
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)
|
||||||
|
require.NoError(t, iterator.Next(context))
|
||||||
|
require.False(t, v.Estack().Pop().Bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("normal invocation", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{0x01}, istorage.FindDefault, []stackitem.Item{
|
||||||
|
stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2]),
|
||||||
|
stackitem.NewByteArray(items[2]),
|
||||||
|
}),
|
||||||
|
stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[0]),
|
||||||
|
stackitem.NewByteArray(items[0]),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("keys only", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{0x01}, istorage.FindKeysOnly, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2]),
|
||||||
|
stackitem.NewByteArray(skeys[0]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("remove prefix", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{0x01}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[2][1:]),
|
||||||
|
stackitem.NewByteArray(skeys[0][1:]),
|
||||||
|
})
|
||||||
|
testFind(t, []byte{0x09, 0x12}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(skeys[8][2:]),
|
||||||
|
stackitem.NewByteArray(skeys[9][2:]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("values only", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{0x01}, istorage.FindValuesOnly, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(items[2]),
|
||||||
|
stackitem.NewByteArray(items[0]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("deserialize values", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{0x04}, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(items[3][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}))
|
||||||
|
err := storageFind(context)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var iter *stackitem.Interop
|
||||||
|
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, []byte{0x06}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
|
||||||
|
testFind(t, []byte{0x06}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
|
||||||
|
// Array with 0 elements.
|
||||||
|
testFind(t, []byte{0x07}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
|
// Array with 1 element.
|
||||||
|
testFind(t, []byte{0x08}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
|
// Not an array, but serialized ByteArray.
|
||||||
|
testFind(t, []byte{0x04}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||||
|
[]stackitem.Item{nil})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("normal invocation, empty result", func(t *testing.T) {
|
||||||
|
testFind(t, []byte{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) {
|
||||||
|
v.Estack().PushVal(istorage.FindDefault)
|
||||||
|
v.Estack().PushVal([]byte{0x01})
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(nil))
|
||||||
|
|
||||||
|
require.Error(t, storageFind(context))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid id", func(t *testing.T) {
|
||||||
|
invalidID := id + 1
|
||||||
|
|
||||||
|
v.Estack().PushVal(istorage.FindDefault)
|
||||||
|
v.Estack().PushVal([]byte{0x01})
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
|
||||||
|
|
||||||
|
require.NoError(t, storageFind(context))
|
||||||
|
require.NoError(t, iterator.Next(context))
|
||||||
|
require.False(t, v.Estack().Pop().Bool())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
||||||
|
|
||||||
|
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
context := chain.newInteropContext(trigger.Application,
|
||||||
|
dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader), nil, nil)
|
||||||
|
v := context.SpawnVM()
|
||||||
|
return v, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
||||||
|
v, block, context, chain := createVMAndBlock(t)
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(block))
|
||||||
|
return v, block, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
|
||||||
|
block := newDumbBlock()
|
||||||
|
chain := newTestChain(t)
|
||||||
|
d := dao.NewSimple(storage.NewMemoryStore(), chain.GetConfig().StateRootInHeader)
|
||||||
|
context := chain.newInteropContext(trigger.Application, d, block, nil)
|
||||||
|
v := context.SpawnVM()
|
||||||
|
return v, block, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
||||||
|
v, tx, context, chain := createVMAndTX(t)
|
||||||
|
v.Estack().PushVal(stackitem.NewInterop(tx))
|
||||||
|
return v, tx, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
|
||||||
|
script := []byte("testscript")
|
||||||
|
m := manifest.NewManifest("Test")
|
||||||
|
ne, err := nef.NewFile(script)
|
||||||
|
require.NoError(t, err)
|
||||||
|
contractState := &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: *ne,
|
||||||
|
Hash: hash.Hash160(script),
|
||||||
|
Manifest: *m,
|
||||||
|
ID: 123,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
chain := newTestChain(t)
|
||||||
|
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader)
|
||||||
|
context := chain.newInteropContext(trigger.Application, d, nil, nil)
|
||||||
|
v := context.SpawnVM()
|
||||||
|
return v, contractState, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
||||||
|
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
||||||
|
tx := transaction.New(script, 0)
|
||||||
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}}
|
||||||
|
tx.Scripts = []transaction.Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}}
|
||||||
|
chain := newTestChain(t)
|
||||||
|
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader)
|
||||||
|
context := chain.newInteropContext(trigger.Application, d, nil, tx)
|
||||||
|
v := context.SpawnVM()
|
||||||
|
return v, tx, context, chain
|
||||||
|
}
|
||||||
|
|
||||||
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
||||||
func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
mgmtHash := bc.ManagementContractHash()
|
mgmtHash := bc.ManagementContractHash()
|
||||||
|
|
Loading…
Add table
Reference in a new issue