kv_store.gno
7.29 Kb ยท 288 lines
1package store
2
3import (
4 "chain/runtime"
5
6 "gno.land/p/nt/avl"
7)
8
9// kvStore represents a domain-specific key-value storage
10// Each domain (pool, position, etc.) creates its own kvStore instance
11type kvStore struct {
12 data map[string]any // key -> value
13 authorizedCallers map[address]Permission
14 domainAddress address
15}
16
17// NewKVStore creates a new kvStore instance for a specific domain
18// domain: the name of the domain using this store (e.g., "pool", "position")
19func NewKVStore(domainAddress address) KVStore {
20 return &kvStore{
21 data: make(map[string]any),
22 authorizedCallers: map[address]Permission{
23 domainAddress: Write,
24 },
25 domainAddress: domainAddress,
26 }
27}
28
29// GetDomainAddress returns the domain address
30func (k *kvStore) GetDomainAddress() address {
31 return k.domainAddress
32}
33
34// GetAllKeys returns all keys stored in this kvStore
35func (k *kvStore) GetAllKeys() ([]string, error) {
36 keys := make([]string, 0, len(k.data))
37
38 // Keys are namespace-prefixed (domainAddress:key) by design
39 for key := range k.data {
40 keys = append(keys, key)
41 }
42
43 return keys, nil
44}
45
46// Has checks if a key exists in the store
47func (k *kvStore) Has(key string) bool {
48 _, exists := k.data[k.makeKey(key)]
49
50 return exists
51}
52
53// Get retrieves a value by key
54// Checks read permission before returning the value
55func (k *kvStore) Get(key string) (any, error) {
56 currentRealm := runtime.CurrentRealm()
57
58 if currentRealm.IsCode() && !k.IsReadAuthorized(currentRealm.Address()) {
59 return nil, ErrReadPermissionDenied
60 }
61
62 value, exists := k.data[k.makeKey(key)]
63 if !exists {
64 return nil, ErrKeyNotFound
65 }
66
67 return value, nil
68}
69
70// GetInt64 retrieves a value by key and casts it to int64
71// Returns ErrFailedCast if the value is not of type int64
72func (k *kvStore) GetInt64(key string) (int64, error) {
73 result, err := k.Get(key)
74 if err != nil {
75 return 0, err
76 }
77
78 return castToInt64(result)
79}
80
81// GetUint64 retrieves a value by key and casts it to uint64
82// Returns ErrFailedCast if the value is not of type uint64
83func (k *kvStore) GetUint64(key string) (uint64, error) {
84 result, err := k.Get(key)
85 if err != nil {
86 return 0, err
87 }
88
89 return castToUint64(result)
90}
91
92// GetBool retrieves a value by key and casts it to bool
93// Returns ErrFailedCast if the value is not of type bool
94func (k *kvStore) GetBool(key string) (bool, error) {
95 result, err := k.Get(key)
96 if err != nil {
97 return false, err
98 }
99
100 return castToBool(result)
101}
102
103// GetString retrieves a value by key and casts it to string
104// Returns ErrFailedCast if the value is not of type string
105func (k *kvStore) GetString(key string) (string, error) {
106 result, err := k.Get(key)
107 if err != nil {
108 return "", err
109 }
110
111 return castToString(result)
112}
113
114// GetAddress retrieves a value by key and casts it to address
115// Returns ErrFailedCast if the value is not of type address
116func (k *kvStore) GetAddress(key string) (address, error) {
117 result, err := k.Get(key)
118 if err != nil {
119 return address(""), err
120 }
121
122 return castToAddress(result)
123}
124
125// GetTree retrieves a value by key and casts it to *avl.Tree
126// Returns ErrFailedCast if the value is not of type *avl.Tree
127func (k *kvStore) GetTree(key string) (*avl.Tree, error) {
128 result, err := k.Get(key)
129 if err != nil {
130 return nil, err
131 }
132
133 return castToTree(result)
134}
135
136// Set stores a value with the given key
137// Checks write permission before setting the value
138func (k *kvStore) Set(key string, value any) error {
139 currentRealm := runtime.CurrentRealm()
140
141 if currentRealm.IsCode() && !k.IsWriteAuthorized(currentRealm.Address()) {
142 return ErrWritePermissionDenied
143 }
144
145 k.data[k.makeKey(key)] = value
146
147 return nil
148}
149
150// Delete removes a key from the store
151// Checks write permission before deleting
152func (k *kvStore) Delete(key string) error {
153 caller := runtime.CurrentRealm().Address()
154
155 // Unlike `Get` and `Set`, this function does not perform `IsCode` checks.
156 // As a package providing generic storage functionality,
157 // `kvStore` focuses on namespace-based data isolation to prevent data
158 // contamination across domains.
159 //
160 // More permission controls (e.g., restricting which keys can be deleted)
161 // should be implemented at the realm level based on specific requirements.
162 if !k.IsWriteAuthorized(caller) {
163 return ErrWritePermissionDenied
164 }
165
166 if !k.Has(key) {
167 return ErrKeyNotFound
168 }
169
170 delete(k.data, k.makeKey(key))
171
172 return nil
173}
174
175// IsDomainAddress checks if the given address is the domain address
176func (k *kvStore) IsDomainAddress(addr address) bool {
177 return k.domainAddress == addr
178}
179
180// IsReadAuthorized checks if the caller has read permission
181func (k *kvStore) IsReadAuthorized(caller address) bool {
182 if k.IsDomainAddress(caller) {
183 return true
184 }
185
186 if !k.isRegisteredAuthorizedCaller(caller) {
187 return false
188 }
189
190 return k.authorizedCallers[caller] >= ReadOnly
191}
192
193// IsWriteAuthorized checks if the caller has write permission
194func (k *kvStore) IsWriteAuthorized(caller address) bool {
195 if k.IsDomainAddress(caller) {
196 return true
197 }
198
199 if !k.isRegisteredAuthorizedCaller(caller) {
200 return false
201 }
202
203 return k.authorizedCallers[caller] >= Write
204}
205
206// GetAuthorizedCallers returns all authorized callers and their permissions
207func (k *kvStore) GetAuthorizedCallers() (map[address]Permission, error) {
208 if k.authorizedCallers == nil {
209 return make(map[address]Permission), ErrAuthorizedCallerNotFound
210 }
211
212 return k.authorizedCallers, nil
213}
214
215// AddAuthorizedCaller adds a new authorized caller with the specified permission
216func (k *kvStore) AddAuthorizedCaller(caller address, permission Permission) error {
217 if !k.isUpdatableAuthorizedCaller() {
218 return ErrUpdatePermissionDenied
219 }
220
221 if k.isRegisteredAuthorizedCaller(caller) {
222 return ErrAuthorizedCallerAlreadyRegistered
223 }
224
225 if !isValidPermission(permission) {
226 return ErrInvalidPermission
227 }
228
229 k.authorizedCallers[caller] = permission
230
231 return nil
232}
233
234// UpdateAuthorizedCaller updates the permission of an existing authorized caller
235func (k *kvStore) UpdateAuthorizedCaller(caller address, permission Permission) error {
236 if !k.isUpdatableAuthorizedCaller() {
237 return ErrUpdatePermissionDenied
238 }
239
240 if !k.isRegisteredAuthorizedCaller(caller) {
241 return ErrAuthorizedCallerNotFound
242 }
243
244 if !isValidPermission(permission) {
245 return ErrInvalidPermission
246 }
247
248 k.authorizedCallers[caller] = permission
249
250 return nil
251}
252
253// RemoveAuthorizedCaller removes an authorized caller
254func (k *kvStore) RemoveAuthorizedCaller(caller address) error {
255 if !k.isUpdatableAuthorizedCaller() {
256 return ErrUpdatePermissionDenied
257 }
258
259 if !k.isRegisteredAuthorizedCaller(caller) {
260 return ErrAuthorizedCallerNotFound
261 }
262
263 delete(k.authorizedCallers, caller)
264
265 return nil
266}
267
268// isRegisteredAuthorizedCaller checks if a caller is registered
269func (k *kvStore) isRegisteredAuthorizedCaller(caller address) bool {
270 _, exists := k.authorizedCallers[caller]
271
272 return exists
273}
274
275// isUpdatableAuthorizedCaller checks if the current realm is the same as the domain address
276func (k *kvStore) isUpdatableAuthorizedCaller() bool {
277 return runtime.CurrentRealm().Address() == k.domainAddress
278}
279
280// makeKey creates a prefixed key with the domain address to ensure isolation
281func (k *kvStore) makeKey(key string) string {
282 return string(k.domainAddress) + ":" + key
283}
284
285// isValidPermission ensures only ReadOnly and Write permissions are assignable via registration APIs.
286func isValidPermission(permission Permission) bool {
287 return permission == ReadOnly || permission == Write
288}