Search Apps Documentation Source Content File Folder Download Copy Actions Download

kv_store.gno

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