Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}