diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go deleted file mode 100644 index 2858a1c8b..000000000 --- a/pkg/core/interop_neo.go +++ /dev/null @@ -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 -} diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go deleted file mode 100644 index 68c2bc95f..000000000 --- a/pkg/core/interop_neo_test.go +++ /dev/null @@ -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 -} diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index eb17b7ee7..e009fa778 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -1,13 +1,16 @@ package core import ( + "bytes" "crypto/elliptic" "errors" "fmt" "math" + "sort" "github.com/nspcc-dev/neo-go/pkg/core/block" "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/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -17,6 +20,11 @@ import ( "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 // a context for storage manipulation functions. type StorageContext struct { @@ -153,6 +161,55 @@ func storageContextAsReadOnly(ic *interop.Context) error { 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 // given m and a set of public keys. func contractCreateMultisigAccount(ic *interop.Context) error { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index f98ecde0f..7c6f714ef 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -7,10 +7,14 @@ import ( "testing" "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/contract" "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" + 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/state" "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/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/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. func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { mgmtHash := bc.ManagementContractHash()