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 }