Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

kv_store.gno

12.95 Kb · 411 lines
  1package store
  2
  3import (
  4	"errors"
  5
  6	bptree "gno.land/p/nt/bptree/v0"
  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// domainAddress is the address of the domain realm that owns this store.
 19//
 20// Parameters:
 21//   - domainAddress: Address of the owning domain realm; it is initially granted Write permission.
 22//
 23// Returns:
 24//   - KVStore: Empty domain-isolated store whose ACL initially authorizes domainAddress for writes.
 25func NewKVStore(domainAddress address) KVStore {
 26	return &kvStore{
 27		data: make(map[string]any),
 28		authorizedCallers: map[address]Permission{
 29			domainAddress: Write,
 30		},
 31		domainAddress: domainAddress,
 32	}
 33}
 34
 35// GetDomainAddress returns the domain address.
 36//
 37// Returns:
 38//   - address: Address of the domain realm that owns this store.
 39func (k *kvStore) GetDomainAddress() address {
 40	return k.domainAddress
 41}
 42
 43// GetAllKeys returns all keys stored in this kvStore.
 44//
 45// Returns:
 46//   - []string: Stored keys including the domain-address namespace prefix; map iteration order is unspecified.
 47//   - error: Always nil for this in-memory implementation.
 48func (k *kvStore) GetAllKeys() ([]string, error) {
 49	keys := make([]string, 0, len(k.data))
 50
 51	// Keys are namespace-prefixed (domainAddress:key) by design
 52	for key := range k.data {
 53		keys = append(keys, key)
 54	}
 55
 56	return keys, nil
 57}
 58
 59// Has checks if a key exists in the store.
 60//
 61// Parameters:
 62//   - key: Logical key to check; the store applies its domain namespace prefix before lookup.
 63//
 64// Returns:
 65//   - bool: true when the namespaced key is present, including when its stored value is nil; false otherwise.
 66func (k *kvStore) Has(key string) bool {
 67	_, exists := k.data[k.makeKey(key)]
 68
 69	return exists
 70}
 71
 72// Get retrieves a value by key.
 73// Reads are public within the package; only writes are gated by the ACL.
 74// Realms exposing sensitive values must enforce their own read policy before
 75// calling into the store.
 76//
 77// Parameters:
 78//   - key: Logical key whose namespaced entry should be retrieved.
 79//
 80// Returns:
 81//   - any: Stored value, including an explicitly stored nil, or nil when key is absent.
 82//   - error: nil when key exists; otherwise ErrKeyNotFound.
 83func (k *kvStore) Get(key string) (any, error) {
 84	value, exists := k.data[k.makeKey(key)]
 85	if !exists {
 86		return nil, errors.New(ErrKeyNotFound)
 87	}
 88
 89	return value, nil
 90}
 91
 92// GetInt64 retrieves a value by key and casts it to int64.
 93// Returns ErrFailedCast if the value is not of type int64.
 94//
 95// Parameters:
 96//   - key: Logical key whose stored value must have dynamic type int64.
 97//
 98// Returns:
 99//   - int64: Stored int64 value, or 0 when the key is absent or has another type.
100//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
101func (k *kvStore) GetInt64(key string) (int64, error) {
102	result, err := k.Get(key)
103	if err != nil {
104		return 0, err
105	}
106
107	return castToInt64(result)
108}
109
110// GetUint64 retrieves a value by key and casts it to uint64.
111// Returns ErrFailedCast if the value is not of type uint64.
112//
113// Parameters:
114//   - key: Logical key whose stored value must have dynamic type uint64.
115//
116// Returns:
117//   - uint64: Stored uint64 value, or 0 when the key is absent or has another type.
118//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
119func (k *kvStore) GetUint64(key string) (uint64, error) {
120	result, err := k.Get(key)
121	if err != nil {
122		return 0, err
123	}
124
125	return castToUint64(result)
126}
127
128// GetBool retrieves a value by key and casts it to bool.
129// Returns ErrFailedCast if the value is not of type bool.
130//
131// Parameters:
132//   - key: Logical key whose stored value must have dynamic type bool.
133//
134// Returns:
135//   - bool: Stored bool value, or false when the key is absent or has another type.
136//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
137func (k *kvStore) GetBool(key string) (bool, error) {
138	result, err := k.Get(key)
139	if err != nil {
140		return false, err
141	}
142
143	return castToBool(result)
144}
145
146// GetString retrieves a value by key and casts it to string.
147// Returns ErrFailedCast if the value is not of type string.
148//
149// Parameters:
150//   - key: Logical key whose stored value must have dynamic type string.
151//
152// Returns:
153//   - string: Stored string value, or the empty string when the key is absent or has another type.
154//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
155func (k *kvStore) GetString(key string) (string, error) {
156	result, err := k.Get(key)
157	if err != nil {
158		return "", err
159	}
160
161	return castToString(result)
162}
163
164// GetAddress retrieves a value by key and casts it to address.
165// Returns ErrFailedCast if the value is not of type address.
166//
167// Parameters:
168//   - key: Logical key whose stored value must have dynamic type address.
169//
170// Returns:
171//   - address: Stored address value, or the empty address when the key is absent or has another type.
172//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
173func (k *kvStore) GetAddress(key string) (address, error) {
174	result, err := k.Get(key)
175	if err != nil {
176		return address(""), err
177	}
178
179	return castToAddress(result)
180}
181
182// GetBPTree retrieves a value by key and casts it to *bptree.BPTree.
183// Returns ErrFailedCast if the value is not of type *bptree.BPTree.
184//
185// Parameters:
186//   - key: Logical key whose stored value must have dynamic type *bptree.BPTree.
187//
188// Returns:
189//   - *bptree.BPTree: Stored B+ tree pointer, or nil when the key is absent or has another type.
190//   - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
191func (k *kvStore) GetBPTree(key string) (*bptree.BPTree, error) {
192	result, err := k.Get(key)
193	if err != nil {
194		return nil, err
195	}
196
197	return castToBPTree(result)
198}
199
200// Set stores a value with the given key.
201//
202// Parameters:
203//   - _: Interrealm-call discriminator; callers pass 0.
204//   - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and uses rlm.Address() for code-caller write authorization.
205//   - key: Logical key under which value is stored; the store adds its domain namespace prefix.
206//   - value: Arbitrary value to store under key.
207//
208// Returns:
209//   - error: nil when value is stored; otherwise ErrSpoofedRealm or ErrWritePermissionDenied.
210func (k *kvStore) Set(_ int, rlm realm, key string, value any) error {
211	if !rlm.IsCurrent() {
212		return errors.New(ErrSpoofedRealm)
213	}
214
215	if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
216		return errors.New(ErrWritePermissionDenied)
217	}
218
219	k.data[k.makeKey(key)] = value
220
221	return nil
222}
223
224// Delete removes a key from the store.
225//
226// Parameters:
227//   - _: Interrealm-call discriminator; callers pass 0.
228//   - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and uses rlm.Address() for code-caller write authorization.
229//   - key: Logical key whose namespaced entry should be removed.
230//
231// Returns:
232//   - error: nil when key is removed; otherwise ErrSpoofedRealm, ErrWritePermissionDenied, or ErrKeyNotFound.
233func (k *kvStore) Delete(_ int, rlm realm, key string) error {
234	if !rlm.IsCurrent() {
235		return errors.New(ErrSpoofedRealm)
236	}
237
238	if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
239		return errors.New(ErrWritePermissionDenied)
240	}
241
242	if !k.Has(key) {
243		return errors.New(ErrKeyNotFound)
244	}
245
246	delete(k.data, k.makeKey(key))
247
248	return nil
249}
250
251// IsDomainAddress checks if the given address is the domain address.
252//
253// Parameters:
254//   - addr: Address to compare with the store's owning domain address.
255//
256// Returns:
257//   - bool: true when addr equals the owning domain address; false otherwise.
258func (k *kvStore) IsDomainAddress(addr address) bool {
259	return k.domainAddress == addr
260}
261
262// IsWriteAuthorized checks if the caller has write permission.
263//
264// Parameters:
265//   - caller: Address whose write permission should be evaluated.
266//
267// Returns:
268//   - bool: true for the domain address or a registered caller with Write permission; false otherwise.
269func (k *kvStore) IsWriteAuthorized(caller address) bool {
270	if k.IsDomainAddress(caller) {
271		return true
272	}
273
274	if !k.isRegisteredAuthorizedCaller(caller) {
275		return false
276	}
277
278	return k.authorizedCallers[caller] >= Write
279}
280
281// GetAuthorizedCallers returns a copy of the authorized callers map with their permissions.
282// A copy is returned so callers cannot mutate the ACL map directly.
283//
284// Returns:
285//   - map[address]Permission: Newly allocated snapshot of registered caller permissions.
286//   - error: nil when the ACL map exists; otherwise ErrAuthorizedCallerNotFound.
287func (k *kvStore) GetAuthorizedCallers() (map[address]Permission, error) {
288	if k.authorizedCallers == nil {
289		return make(map[address]Permission), errors.New(ErrAuthorizedCallerNotFound)
290	}
291
292	out := make(map[address]Permission, len(k.authorizedCallers))
293	for addr, perm := range k.authorizedCallers {
294		out[addr] = perm
295	}
296	return out, nil
297}
298
299// AddAuthorizedCaller adds a new authorized caller with the specified permission.
300//
301// Parameters:
302//   - _: Interrealm-call discriminator; callers pass 0.
303//   - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
304//   - caller: Address to add to the write-authorization map.
305//   - permission: Permission to assign; registration APIs accept only Write.
306//
307// Returns:
308//   - error: nil when caller is added; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, ErrAuthorizedCallerAlreadyRegistered, or ErrInvalidPermission.
309func (k *kvStore) AddAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
310	if !rlm.IsCurrent() {
311		return errors.New(ErrSpoofedRealm)
312	}
313
314	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
315		return errors.New(ErrUpdatePermissionDenied)
316	}
317
318	if k.isRegisteredAuthorizedCaller(caller) {
319		return errors.New(ErrAuthorizedCallerAlreadyRegistered)
320	}
321
322	if !isValidPermission(permission) {
323		return errors.New(ErrInvalidPermission)
324	}
325
326	k.authorizedCallers[caller] = permission
327
328	return nil
329}
330
331// UpdateAuthorizedCaller updates the permission of an existing authorized caller.
332//
333// Parameters:
334//   - _: Interrealm-call discriminator; callers pass 0.
335//   - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
336//   - caller: Address of the already registered caller whose permission should change.
337//   - permission: Replacement permission; registration APIs accept only Write.
338//
339// Returns:
340//   - error: nil when caller's permission is updated; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, ErrAuthorizedCallerNotFound, or ErrInvalidPermission.
341func (k *kvStore) UpdateAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
342	if !rlm.IsCurrent() {
343		return errors.New(ErrSpoofedRealm)
344	}
345
346	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
347		return errors.New(ErrUpdatePermissionDenied)
348	}
349
350	if !k.isRegisteredAuthorizedCaller(caller) {
351		return errors.New(ErrAuthorizedCallerNotFound)
352	}
353
354	if !isValidPermission(permission) {
355		return errors.New(ErrInvalidPermission)
356	}
357
358	k.authorizedCallers[caller] = permission
359
360	return nil
361}
362
363// RemoveAuthorizedCaller removes an authorized caller.
364//
365// Parameters:
366//   - _: Interrealm-call discriminator; callers pass 0.
367//   - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
368//   - caller: Address of the registered caller to remove.
369//
370// Returns:
371//   - error: nil when caller is removed; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, or ErrAuthorizedCallerNotFound.
372func (k *kvStore) RemoveAuthorizedCaller(_ int, rlm realm, caller address) error {
373	if !rlm.IsCurrent() {
374		return errors.New(ErrSpoofedRealm)
375	}
376
377	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
378		return errors.New(ErrUpdatePermissionDenied)
379	}
380
381	if !k.isRegisteredAuthorizedCaller(caller) {
382		return errors.New(ErrAuthorizedCallerNotFound)
383	}
384
385	delete(k.authorizedCallers, caller)
386
387	return nil
388}
389
390// isRegisteredAuthorizedCaller checks if a caller is registered
391func (k *kvStore) isRegisteredAuthorizedCaller(caller address) bool {
392	_, exists := k.authorizedCallers[caller]
393
394	return exists
395}
396
397// isUpdatableAuthorizedCaller checks if the current realm is the same as the domain address
398func (k *kvStore) isUpdatableAuthorizedCaller(currentRealmAddress address) bool {
399	return currentRealmAddress == k.domainAddress
400}
401
402// makeKey creates a prefixed key with the domain address to ensure isolation
403func (k *kvStore) makeKey(key string) string {
404	return string(k.domainAddress) + ":" + key
405}
406
407// isValidPermission ensures only Write is assignable via registration APIs.
408// The zero value is rejected so callers must spell out an explicit permission.
409func isValidPermission(permission Permission) bool {
410	return permission == Write
411}