2022-06-08 16:31:49 +00:00
|
|
|
package storage_test
|
2020-06-08 15:36:19 +00:00
|
|
|
|
|
|
|
import (
|
2020-10-05 09:32:04 +00:00
|
|
|
"errors"
|
2020-06-08 15:36:19 +00:00
|
|
|
"math/big"
|
|
|
|
"testing"
|
|
|
|
|
2022-07-08 16:51:59 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/limits"
|
2022-06-08 16:31:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
2020-07-28 10:17:38 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2021-05-11 14:51:45 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
|
|
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
2020-12-13 15:26:35 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
2020-06-16 08:17:18 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2022-06-08 16:31:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2020-06-16 09:47:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
2022-06-08 16:31:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
2020-12-29 10:45:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2020-07-16 08:32:32 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
2021-01-13 12:34:10 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
2021-05-11 14:51:45 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
2021-04-29 08:33:21 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
2020-06-08 15:36:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
func TestPut(t *testing.T) {
|
2022-06-08 13:41:28 +00:00
|
|
|
_, cs, ic, _ := createVMAndContractState(t)
|
2020-10-05 09:32:04 +00:00
|
|
|
|
2022-06-08 13:41:28 +00:00
|
|
|
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
2020-10-05 09:32:04 +00:00
|
|
|
|
|
|
|
initVM := func(t *testing.T, key, value []byte, gas int64) {
|
|
|
|
v := ic.SpawnVM()
|
2021-01-13 12:34:10 +00:00
|
|
|
v.LoadScript(cs.NEF.Script)
|
2020-10-05 09:32:04 +00:00
|
|
|
v.GasLimit = gas
|
|
|
|
v.Estack().PushVal(value)
|
|
|
|
v.Estack().PushVal(key)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.GetContext(ic))
|
2020-10-05 09:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("create, not enough gas", func(t *testing.T) {
|
2021-02-02 15:46:43 +00:00
|
|
|
initVM(t, []byte{1}, []byte{2, 3}, 2*native.DefaultStoragePrice)
|
2022-06-08 16:31:49 +00:00
|
|
|
err := istorage.Put(ic)
|
|
|
|
require.True(t, errors.Is(err, istorage.ErrGasLimitExceeded), "got: %v", err)
|
2020-10-05 09:32:04 +00:00
|
|
|
})
|
|
|
|
|
2021-02-02 15:46:43 +00:00
|
|
|
initVM(t, []byte{4}, []byte{5, 6}, 3*native.DefaultStoragePrice)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.Put(ic))
|
2020-10-05 09:32:04 +00:00
|
|
|
|
|
|
|
t.Run("update", func(t *testing.T) {
|
|
|
|
t.Run("not enough gas", func(t *testing.T) {
|
2021-02-02 15:46:43 +00:00
|
|
|
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.DefaultStoragePrice)
|
2022-06-08 16:31:49 +00:00
|
|
|
err := istorage.Put(ic)
|
|
|
|
require.True(t, errors.Is(err, istorage.ErrGasLimitExceeded), "got: %v", err)
|
2020-10-05 09:32:04 +00:00
|
|
|
})
|
2021-02-02 15:46:43 +00:00
|
|
|
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.DefaultStoragePrice)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.Put(ic))
|
2021-02-02 15:46:43 +00:00
|
|
|
initVM(t, []byte{4}, []byte{5, 6}, native.DefaultStoragePrice)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.Put(ic))
|
2020-10-05 09:32:04 +00:00
|
|
|
})
|
2020-12-02 10:13:42 +00:00
|
|
|
|
|
|
|
t.Run("check limits", func(t *testing.T) {
|
2022-07-08 16:51:59 +00:00
|
|
|
initVM(t, make([]byte, limits.MaxStorageKeyLen), make([]byte, limits.MaxStorageValueLen), -1)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.Put(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("bad", func(t *testing.T) {
|
|
|
|
t.Run("readonly context", func(t *testing.T) {
|
|
|
|
initVM(t, []byte{1}, []byte{1}, -1)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.ContextAsReadOnly(ic))
|
|
|
|
require.Error(t, istorage.Put(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
t.Run("big key", func(t *testing.T) {
|
2022-07-08 16:51:59 +00:00
|
|
|
initVM(t, make([]byte, limits.MaxStorageKeyLen+1), []byte{1}, -1)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.Error(t, istorage.Put(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
t.Run("big value", func(t *testing.T) {
|
2022-07-08 16:51:59 +00:00
|
|
|
initVM(t, []byte{1}, make([]byte, limits.MaxStorageValueLen+1), -1)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.Error(t, istorage.Put(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
func TestDelete(t *testing.T) {
|
2022-06-08 13:41:28 +00:00
|
|
|
v, cs, ic, _ := createVMAndContractState(t)
|
2020-12-02 10:13:42 +00:00
|
|
|
|
2022-06-08 13:41:28 +00:00
|
|
|
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
2020-12-29 10:45:49 +00:00
|
|
|
v.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
2020-12-02 10:13:42 +00:00
|
|
|
put := func(key, value string, flag int) {
|
|
|
|
v.Estack().PushVal(value)
|
|
|
|
v.Estack().PushVal(key)
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.GetContext(ic))
|
|
|
|
require.NoError(t, istorage.Put(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
}
|
|
|
|
put("key1", "value1", 0)
|
|
|
|
put("key2", "value2", 0)
|
|
|
|
put("key3", "value3", 0)
|
|
|
|
|
|
|
|
t.Run("good", func(t *testing.T) {
|
|
|
|
v.Estack().PushVal("key1")
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.GetContext(ic))
|
|
|
|
require.NoError(t, istorage.Delete(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
t.Run("readonly context", func(t *testing.T) {
|
|
|
|
v.Estack().PushVal("key2")
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.GetReadOnlyContext(ic))
|
|
|
|
require.Error(t, istorage.Delete(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
|
|
|
t.Run("readonly context (from normal)", func(t *testing.T) {
|
|
|
|
v.Estack().PushVal("key3")
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.GetContext(ic))
|
|
|
|
require.NoError(t, istorage.ContextAsReadOnly(ic))
|
|
|
|
require.Error(t, istorage.Delete(ic))
|
2020-12-02 10:13:42 +00:00
|
|
|
})
|
2020-10-05 09:32:04 +00:00
|
|
|
}
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
func TestFind(t *testing.T) {
|
|
|
|
v, contractState, context, _ := createVMAndContractState(t)
|
2021-05-11 14:51:45 +00:00
|
|
|
|
|
|
|
arr := []stackitem.Item{
|
|
|
|
stackitem.NewBigInteger(big.NewInt(42)),
|
|
|
|
stackitem.NewByteArray([]byte("second")),
|
|
|
|
stackitem.Null{},
|
|
|
|
}
|
2021-07-06 16:56:23 +00:00
|
|
|
rawArr, err := stackitem.Serialize(stackitem.NewArray(arr))
|
2021-05-11 14:51:45 +00:00
|
|
|
require.NoError(t, err)
|
2021-07-06 16:56:23 +00:00
|
|
|
rawArr0, err := stackitem.Serialize(stackitem.NewArray(arr[:0]))
|
2021-05-11 14:51:45 +00:00
|
|
|
require.NoError(t, err)
|
2021-07-06 16:56:23 +00:00
|
|
|
rawArr1, err := stackitem.Serialize(stackitem.NewArray(arr[:1]))
|
2021-05-11 14:51:45 +00:00
|
|
|
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},
|
|
|
|
}
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, native.PutContractState(context.DAO, contractState))
|
2021-05-11 14:51:45 +00:00
|
|
|
|
|
|
|
id := contractState.ID
|
|
|
|
|
|
|
|
for i := range skeys {
|
2022-02-16 14:48:15 +00:00
|
|
|
context.DAO.PutStorageItem(id, skeys[i], items[i])
|
2021-05-11 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
|
|
|
|
v.Estack().PushVal(opts)
|
|
|
|
v.Estack().PushVal(prefix)
|
2022-06-08 16:31:49 +00:00
|
|
|
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
2021-05-11 14:51:45 +00:00
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
err := istorage.Find(context)
|
2021-05-11 14:51:45 +00:00
|
|
|
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})
|
2022-06-08 16:31:49 +00:00
|
|
|
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
|
|
|
err := istorage.Find(context)
|
2021-05-11 14:51:45 +00:00
|
|
|
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})
|
2022-06-08 16:31:49 +00:00
|
|
|
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
|
|
|
require.Error(t, istorage.Find(context))
|
2021-05-11 14:51:45 +00:00
|
|
|
}
|
|
|
|
})
|
2022-06-08 16:31:49 +00:00
|
|
|
t.Run("invalid type for storage.Context", func(t *testing.T) {
|
2021-05-11 14:51:45 +00:00
|
|
|
v.Estack().PushVal(istorage.FindDefault)
|
|
|
|
v.Estack().PushVal([]byte{0x01})
|
|
|
|
v.Estack().PushVal(stackitem.NewInterop(nil))
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
require.Error(t, istorage.Find(context))
|
2021-05-11 14:51:45 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("invalid id", func(t *testing.T) {
|
|
|
|
invalidID := id + 1
|
|
|
|
|
|
|
|
v.Estack().PushVal(istorage.FindDefault)
|
|
|
|
v.Estack().PushVal([]byte{0x01})
|
2022-06-08 16:31:49 +00:00
|
|
|
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: invalidID}))
|
2021-05-11 14:51:45 +00:00
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
require.NoError(t, istorage.Find(context))
|
2021-05-11 14:51:45 +00:00
|
|
|
require.NoError(t, iterator.Next(context))
|
|
|
|
require.False(t, v.Estack().Pop().Bool())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
|
|
|
|
chain, _ := chain.NewSingle(t)
|
|
|
|
ic := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
|
|
|
|
v := ic.SpawnVM()
|
|
|
|
return v, ic, chain
|
2021-05-11 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-06-08 16:31:49 +00:00
|
|
|
func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.Context, *core.Blockchain) {
|
2021-05-11 14:51:45 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-04-12 14:29:11 +00:00
|
|
|
v, context, chain := createVM(t)
|
2021-05-11 14:51:45 +00:00
|
|
|
return v, contractState, context, chain
|
|
|
|
}
|