github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/storage/storage_test.go (about)

     1  package storage_test
     2  
     3  import (
     4  	"math/big"
     5  	"testing"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/config/limits"
     8  	"github.com/nspcc-dev/neo-go/pkg/core"
     9  	"github.com/nspcc-dev/neo-go/pkg/core/block"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
    12  	istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/native"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    16  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    17  	"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
    18  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    19  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    20  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
    21  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    22  	"github.com/nspcc-dev/neo-go/pkg/vm"
    23  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestPut(t *testing.T) {
    28  	_, cs, ic, _ := createVMAndContractState(t)
    29  
    30  	require.NoError(t, native.PutContractState(ic.DAO, cs))
    31  
    32  	initVM := func(t *testing.T, key, value []byte, gas int64) {
    33  		v := ic.SpawnVM()
    34  		v.LoadScript(cs.NEF.Script)
    35  		v.GasLimit = gas
    36  		v.Estack().PushVal(value)
    37  		v.Estack().PushVal(key)
    38  		require.NoError(t, istorage.GetContext(ic))
    39  	}
    40  
    41  	t.Run("create, not enough gas", func(t *testing.T) {
    42  		initVM(t, []byte{1}, []byte{2, 3}, 2*native.DefaultStoragePrice)
    43  		err := istorage.Put(ic)
    44  		require.ErrorIs(t, err, istorage.ErrGasLimitExceeded)
    45  	})
    46  
    47  	initVM(t, []byte{4}, []byte{5, 6}, 3*native.DefaultStoragePrice)
    48  	require.NoError(t, istorage.Put(ic))
    49  
    50  	t.Run("update", func(t *testing.T) {
    51  		t.Run("not enough gas", func(t *testing.T) {
    52  			initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.DefaultStoragePrice)
    53  			err := istorage.Put(ic)
    54  			require.ErrorIs(t, err, istorage.ErrGasLimitExceeded)
    55  		})
    56  		initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.DefaultStoragePrice)
    57  		require.NoError(t, istorage.Put(ic))
    58  		initVM(t, []byte{4}, []byte{5, 6}, native.DefaultStoragePrice)
    59  		require.NoError(t, istorage.Put(ic))
    60  	})
    61  
    62  	t.Run("check limits", func(t *testing.T) {
    63  		initVM(t, make([]byte, limits.MaxStorageKeyLen), make([]byte, limits.MaxStorageValueLen), -1)
    64  		require.NoError(t, istorage.Put(ic))
    65  	})
    66  
    67  	t.Run("bad", func(t *testing.T) {
    68  		t.Run("readonly context", func(t *testing.T) {
    69  			initVM(t, []byte{1}, []byte{1}, -1)
    70  			require.NoError(t, istorage.ContextAsReadOnly(ic))
    71  			require.Error(t, istorage.Put(ic))
    72  		})
    73  		t.Run("big key", func(t *testing.T) {
    74  			initVM(t, make([]byte, limits.MaxStorageKeyLen+1), []byte{1}, -1)
    75  			require.Error(t, istorage.Put(ic))
    76  		})
    77  		t.Run("big value", func(t *testing.T) {
    78  			initVM(t, []byte{1}, make([]byte, limits.MaxStorageValueLen+1), -1)
    79  			require.Error(t, istorage.Put(ic))
    80  		})
    81  	})
    82  }
    83  
    84  func TestDelete(t *testing.T) {
    85  	v, cs, ic, _ := createVMAndContractState(t)
    86  
    87  	require.NoError(t, native.PutContractState(ic.DAO, cs))
    88  	v.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
    89  	put := func(key, value string, flag int) {
    90  		v.Estack().PushVal(value)
    91  		v.Estack().PushVal(key)
    92  		require.NoError(t, istorage.GetContext(ic))
    93  		require.NoError(t, istorage.Put(ic))
    94  	}
    95  	put("key1", "value1", 0)
    96  	put("key2", "value2", 0)
    97  	put("key3", "value3", 0)
    98  
    99  	t.Run("good", func(t *testing.T) {
   100  		v.Estack().PushVal("key1")
   101  		require.NoError(t, istorage.GetContext(ic))
   102  		require.NoError(t, istorage.Delete(ic))
   103  	})
   104  	t.Run("readonly context", func(t *testing.T) {
   105  		v.Estack().PushVal("key2")
   106  		require.NoError(t, istorage.GetReadOnlyContext(ic))
   107  		require.Error(t, istorage.Delete(ic))
   108  	})
   109  	t.Run("readonly context (from normal)", func(t *testing.T) {
   110  		v.Estack().PushVal("key3")
   111  		require.NoError(t, istorage.GetContext(ic))
   112  		require.NoError(t, istorage.ContextAsReadOnly(ic))
   113  		require.Error(t, istorage.Delete(ic))
   114  	})
   115  }
   116  
   117  func TestFind(t *testing.T) {
   118  	v, contractState, context, _ := createVMAndContractState(t)
   119  
   120  	arr := []stackitem.Item{
   121  		stackitem.NewBigInteger(big.NewInt(42)),
   122  		stackitem.NewByteArray([]byte("second")),
   123  		stackitem.Null{},
   124  	}
   125  	rawArr, err := stackitem.Serialize(stackitem.NewArray(arr))
   126  	require.NoError(t, err)
   127  	rawArr0, err := stackitem.Serialize(stackitem.NewArray(arr[:0]))
   128  	require.NoError(t, err)
   129  	rawArr1, err := stackitem.Serialize(stackitem.NewArray(arr[:1]))
   130  	require.NoError(t, err)
   131  
   132  	skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01},
   133  		{0x04, 0x00}, {0x05, 0x00}, {0x06}, {0x07}, {0x08},
   134  		{0x09, 0x12, 0x34}, {0x09, 0x12, 0x56},
   135  	}
   136  	items := []state.StorageItem{
   137  		[]byte{0x01, 0x02, 0x03, 0x04},
   138  		[]byte{0x04, 0x03, 0x02, 0x01},
   139  		[]byte{0x03, 0x04, 0x05, 0x06},
   140  		[]byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
   141  		[]byte{0xFF, 0xFF},
   142  		rawArr,
   143  		rawArr0,
   144  		rawArr1,
   145  		[]byte{111},
   146  		[]byte{222},
   147  	}
   148  
   149  	require.NoError(t, native.PutContractState(context.DAO, contractState))
   150  
   151  	id := contractState.ID
   152  
   153  	for i := range skeys {
   154  		context.DAO.PutStorageItem(id, skeys[i], items[i])
   155  	}
   156  
   157  	testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
   158  		v.Estack().PushVal(opts)
   159  		v.Estack().PushVal(prefix)
   160  		v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
   161  
   162  		err := istorage.Find(context)
   163  		require.NoError(t, err)
   164  
   165  		var iter *stackitem.Interop
   166  		require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
   167  
   168  		for i := range expected { // sorted indices with mathing prefix
   169  			v.Estack().PushVal(iter)
   170  			require.NoError(t, iterator.Next(context))
   171  			require.True(t, v.Estack().Pop().Bool())
   172  
   173  			v.Estack().PushVal(iter)
   174  			if expected[i] == nil {
   175  				require.Panics(t, func() { _ = iterator.Value(context) })
   176  				return
   177  			}
   178  			require.NoError(t, iterator.Value(context))
   179  			require.Equal(t, expected[i], v.Estack().Pop().Item())
   180  		}
   181  
   182  		v.Estack().PushVal(iter)
   183  		require.NoError(t, iterator.Next(context))
   184  		require.False(t, v.Estack().Pop().Bool())
   185  	}
   186  
   187  	t.Run("normal invocation", func(t *testing.T) {
   188  		testFind(t, []byte{0x01}, istorage.FindDefault, []stackitem.Item{
   189  			stackitem.NewStruct([]stackitem.Item{
   190  				stackitem.NewByteArray(skeys[2]),
   191  				stackitem.NewByteArray(items[2]),
   192  			}),
   193  			stackitem.NewStruct([]stackitem.Item{
   194  				stackitem.NewByteArray(skeys[0]),
   195  				stackitem.NewByteArray(items[0]),
   196  			}),
   197  		})
   198  	})
   199  	t.Run("normal invocation, backwards", func(t *testing.T) {
   200  		testFind(t, []byte{0x01}, istorage.FindBackwards, []stackitem.Item{
   201  			stackitem.NewStruct([]stackitem.Item{
   202  				stackitem.NewByteArray(skeys[0]),
   203  				stackitem.NewByteArray(items[0]),
   204  			}),
   205  			stackitem.NewStruct([]stackitem.Item{
   206  				stackitem.NewByteArray(skeys[2]),
   207  				stackitem.NewByteArray(items[2]),
   208  			}),
   209  		})
   210  	})
   211  	t.Run("keys only", func(t *testing.T) {
   212  		testFind(t, []byte{0x01}, istorage.FindKeysOnly, []stackitem.Item{
   213  			stackitem.NewByteArray(skeys[2]),
   214  			stackitem.NewByteArray(skeys[0]),
   215  		})
   216  	})
   217  	t.Run("remove prefix", func(t *testing.T) {
   218  		testFind(t, []byte{0x01}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
   219  			stackitem.NewByteArray(skeys[2][1:]),
   220  			stackitem.NewByteArray(skeys[0][1:]),
   221  		})
   222  		testFind(t, []byte{0x09, 0x12}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
   223  			stackitem.NewByteArray(skeys[8][2:]),
   224  			stackitem.NewByteArray(skeys[9][2:]),
   225  		})
   226  	})
   227  	t.Run("values only", func(t *testing.T) {
   228  		testFind(t, []byte{0x01}, istorage.FindValuesOnly, []stackitem.Item{
   229  			stackitem.NewByteArray(items[2]),
   230  			stackitem.NewByteArray(items[0]),
   231  		})
   232  	})
   233  	t.Run("deserialize values", func(t *testing.T) {
   234  		testFind(t, []byte{0x04}, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
   235  			stackitem.NewByteArray(items[3][2:]),
   236  		})
   237  		t.Run("invalid", func(t *testing.T) {
   238  			v.Estack().PushVal(istorage.FindDeserialize)
   239  			v.Estack().PushVal([]byte{0x05})
   240  			v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
   241  			err := istorage.Find(context)
   242  			require.NoError(t, err)
   243  
   244  			var iter *stackitem.Interop
   245  			require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
   246  
   247  			v.Estack().PushVal(iter)
   248  			require.NoError(t, iterator.Next(context))
   249  
   250  			v.Estack().PushVal(iter)
   251  			require.Panics(t, func() { _ = iterator.Value(context) })
   252  		})
   253  	})
   254  	t.Run("PickN", func(t *testing.T) {
   255  		testFind(t, []byte{0x06}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
   256  		testFind(t, []byte{0x06}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
   257  		// Array with 0 elements.
   258  		testFind(t, []byte{0x07}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
   259  			[]stackitem.Item{nil})
   260  		// Array with 1 element.
   261  		testFind(t, []byte{0x08}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
   262  			[]stackitem.Item{nil})
   263  		// Not an array, but serialized ByteArray.
   264  		testFind(t, []byte{0x04}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
   265  			[]stackitem.Item{nil})
   266  	})
   267  
   268  	t.Run("normal invocation, empty result", func(t *testing.T) {
   269  		testFind(t, []byte{0x03}, istorage.FindDefault, nil)
   270  	})
   271  
   272  	t.Run("invalid options", func(t *testing.T) {
   273  		invalid := []int64{
   274  			istorage.FindKeysOnly | istorage.FindValuesOnly,
   275  			^istorage.FindAll,
   276  			istorage.FindKeysOnly | istorage.FindDeserialize,
   277  			istorage.FindPick0,
   278  			istorage.FindPick0 | istorage.FindPick1 | istorage.FindDeserialize,
   279  			istorage.FindPick0 | istorage.FindPick1,
   280  		}
   281  		for _, opts := range invalid {
   282  			v.Estack().PushVal(opts)
   283  			v.Estack().PushVal([]byte{0x01})
   284  			v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
   285  			require.Error(t, istorage.Find(context))
   286  		}
   287  	})
   288  	t.Run("invalid type for storage.Context", func(t *testing.T) {
   289  		v.Estack().PushVal(istorage.FindDefault)
   290  		v.Estack().PushVal([]byte{0x01})
   291  		v.Estack().PushVal(stackitem.NewInterop(nil))
   292  
   293  		require.Error(t, istorage.Find(context))
   294  	})
   295  
   296  	t.Run("invalid id", func(t *testing.T) {
   297  		invalidID := id + 1
   298  
   299  		v.Estack().PushVal(istorage.FindDefault)
   300  		v.Estack().PushVal([]byte{0x01})
   301  		v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: invalidID}))
   302  
   303  		require.NoError(t, istorage.Find(context))
   304  		require.NoError(t, iterator.Next(context))
   305  		require.False(t, v.Estack().Pop().Bool())
   306  	})
   307  }
   308  
   309  // Helper functions to create VM, InteropContext, TX, Account, Contract.
   310  
   311  func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
   312  	chain, _ := chain.NewSingle(t)
   313  	ic, err := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
   314  	require.NoError(t, err)
   315  	v := ic.SpawnVM()
   316  	return v, ic, chain
   317  }
   318  
   319  func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.Context, *core.Blockchain) {
   320  	script := []byte("testscript")
   321  	m := manifest.NewManifest("Test")
   322  	ne, err := nef.NewFile(script)
   323  	require.NoError(t, err)
   324  	contractState := &state.Contract{
   325  		ContractBase: state.ContractBase{
   326  			NEF:      *ne,
   327  			Hash:     hash.Hash160(script),
   328  			Manifest: *m,
   329  			ID:       123,
   330  		},
   331  	}
   332  
   333  	v, context, chain := createVM(t)
   334  	return v, contractState, context, chain
   335  }