github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/storage/basic.go (about) 1 package storage 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/nspcc-dev/neo-go/pkg/config/limits" 8 "github.com/nspcc-dev/neo-go/pkg/core/interop" 9 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 10 ) 11 12 var ( 13 // ErrGasLimitExceeded is returned from interops when there is not enough 14 // GAS left in the execution context to complete the action. 15 ErrGasLimitExceeded = errors.New("gas limit exceeded") 16 errFindInvalidOptions = errors.New("invalid Find options") 17 ) 18 19 // Context contains contract ID and read/write flag, it's used as 20 // a context for storage manipulation functions. 21 type Context struct { 22 ID int32 23 ReadOnly bool 24 } 25 26 // storageDelete deletes stored key-value pair. 27 func Delete(ic *interop.Context) error { 28 stcInterface := ic.VM.Estack().Pop().Value() 29 stc, ok := stcInterface.(*Context) 30 if !ok { 31 return fmt.Errorf("%T is not a storage.Context", stcInterface) 32 } 33 if stc.ReadOnly { 34 return errors.New("storage.Context is read only") 35 } 36 key := ic.VM.Estack().Pop().Bytes() 37 ic.DAO.DeleteStorageItem(stc.ID, key) 38 return nil 39 } 40 41 // Get returns stored key-value pair. 42 func Get(ic *interop.Context) error { 43 stcInterface := ic.VM.Estack().Pop().Value() 44 stc, ok := stcInterface.(*Context) 45 if !ok { 46 return fmt.Errorf("%T is not a storage.Context", stcInterface) 47 } 48 key := ic.VM.Estack().Pop().Bytes() 49 si := ic.DAO.GetStorageItem(stc.ID, key) 50 if si != nil { 51 ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si))) 52 } else { 53 ic.VM.Estack().PushItem(stackitem.Null{}) 54 } 55 return nil 56 } 57 58 // GetContext returns storage context for the currently executing contract. 59 func GetContext(ic *interop.Context) error { 60 return getContextInternal(ic, false) 61 } 62 63 // GetReadOnlyContext returns read-only storage context for the currently executing contract. 64 func GetReadOnlyContext(ic *interop.Context) error { 65 return getContextInternal(ic, true) 66 } 67 68 // getContextInternal is internal version of storageGetContext and 69 // storageGetReadOnlyContext which allows to specify ReadOnly context flag. 70 func getContextInternal(ic *interop.Context, isReadOnly bool) error { 71 contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) 72 if err != nil { 73 return fmt.Errorf("storage context can not be retrieved in dynamic scripts: %w", err) 74 } 75 sc := &Context{ 76 ID: contract.ID, 77 ReadOnly: isReadOnly, 78 } 79 ic.VM.Estack().PushItem(stackitem.NewInterop(sc)) 80 return nil 81 } 82 83 func putWithContext(ic *interop.Context, stc *Context, key []byte, value []byte) error { 84 if len(key) > limits.MaxStorageKeyLen { 85 return errors.New("key is too big") 86 } 87 if len(value) > limits.MaxStorageValueLen { 88 return errors.New("value is too big") 89 } 90 if stc.ReadOnly { 91 return errors.New("storage.Context is read only") 92 } 93 si := ic.DAO.GetStorageItem(stc.ID, key) 94 sizeInc := len(value) 95 if si == nil { 96 sizeInc = len(key) + len(value) 97 } else if len(value) != 0 { 98 if len(value) <= len(si) { 99 sizeInc = (len(value)-1)/4 + 1 100 } else if len(si) != 0 { 101 sizeInc = (len(si)-1)/4 + 1 + len(value) - len(si) 102 } 103 } 104 if !ic.VM.AddGas(int64(sizeInc) * ic.BaseStorageFee()) { 105 return ErrGasLimitExceeded 106 } 107 ic.DAO.PutStorageItem(stc.ID, key, value) 108 return nil 109 } 110 111 // Put puts key-value pair into the storage. 112 func Put(ic *interop.Context) error { 113 stcInterface := ic.VM.Estack().Pop().Value() 114 stc, ok := stcInterface.(*Context) 115 if !ok { 116 return fmt.Errorf("%T is not a storage.Context", stcInterface) 117 } 118 key := ic.VM.Estack().Pop().Bytes() 119 value := ic.VM.Estack().Pop().Bytes() 120 return putWithContext(ic, stc, key, value) 121 } 122 123 // ContextAsReadOnly sets given context to read-only mode. 124 func ContextAsReadOnly(ic *interop.Context) error { 125 stcInterface := ic.VM.Estack().Pop().Value() 126 stc, ok := stcInterface.(*Context) 127 if !ok { 128 return fmt.Errorf("%T is not a storage.Context", stcInterface) 129 } 130 if !stc.ReadOnly { 131 stx := &Context{ 132 ID: stc.ID, 133 ReadOnly: true, 134 } 135 stc = stx 136 } 137 ic.VM.Estack().PushItem(stackitem.NewInterop(stc)) 138 return nil 139 }