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

7.53 Kb · 289 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// 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// Reads are public within the package; only writes are gated by the ACL.
 55func (k *kvStore) Get(key string) (any, error) {
 56	value, exists := k.data[k.makeKey(key)]
 57	if !exists {
 58		return nil, errors.New(ErrKeyNotFound)
 59	}
 60
 61	return value, nil
 62}
 63
 64// GetInt64 retrieves a value by key and casts it to int64
 65// Returns ErrFailedCast if the value is not of type int64
 66func (k *kvStore) GetInt64(key string) (int64, error) {
 67	result, err := k.Get(key)
 68	if err != nil {
 69		return 0, err
 70	}
 71
 72	return castToInt64(result)
 73}
 74
 75// GetUint64 retrieves a value by key and casts it to uint64
 76// Returns ErrFailedCast if the value is not of type uint64
 77func (k *kvStore) GetUint64(key string) (uint64, error) {
 78	result, err := k.Get(key)
 79	if err != nil {
 80		return 0, err
 81	}
 82
 83	return castToUint64(result)
 84}
 85
 86// GetBool retrieves a value by key and casts it to bool
 87// Returns ErrFailedCast if the value is not of type bool
 88func (k *kvStore) GetBool(key string) (bool, error) {
 89	result, err := k.Get(key)
 90	if err != nil {
 91		return false, err
 92	}
 93
 94	return castToBool(result)
 95}
 96
 97// GetString retrieves a value by key and casts it to string
 98// Returns ErrFailedCast if the value is not of type string
 99func (k *kvStore) GetString(key string) (string, error) {
100	result, err := k.Get(key)
101	if err != nil {
102		return "", err
103	}
104
105	return castToString(result)
106}
107
108// GetAddress retrieves a value by key and casts it to address
109// Returns ErrFailedCast if the value is not of type address
110func (k *kvStore) GetAddress(key string) (address, error) {
111	result, err := k.Get(key)
112	if err != nil {
113		return address(""), err
114	}
115
116	return castToAddress(result)
117}
118
119// GetBPTree retrieves a value by key and casts it to *bptree.BPTree
120// Returns ErrFailedCast if the value is not of type *bptree.BPTree
121func (k *kvStore) GetBPTree(key string) (*bptree.BPTree, error) {
122	result, err := k.Get(key)
123	if err != nil {
124		return nil, err
125	}
126
127	return castToBPTree(result)
128}
129
130// Set stores a value with the given key.
131//
132// rlm.IsCurrent() must hold (rejects spoofed/stale realm tokens). Code callers
133// must be in the authorized writers set. User-realm (EOA) callers bypass the ACL
134// to support deploy and test flows — symmetric with Delete below.
135func (k *kvStore) Set(_ int, rlm realm, key string, value any) error {
136	if !rlm.IsCurrent() {
137		return errors.New(ErrSpoofedRealm)
138	}
139
140	if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
141		return errors.New(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// Follows the same ACL policy as Set: code callers require authorization,
152// user-realm (EOA) callers bypass the ACL.
153func (k *kvStore) Delete(_ int, rlm realm, key string) error {
154	if !rlm.IsCurrent() {
155		return errors.New(ErrSpoofedRealm)
156	}
157
158	if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
159		return errors.New(ErrWritePermissionDenied)
160	}
161
162	if !k.Has(key) {
163		return errors.New(ErrKeyNotFound)
164	}
165
166	delete(k.data, k.makeKey(key))
167
168	return nil
169}
170
171// IsDomainAddress checks if the given address is the domain address
172func (k *kvStore) IsDomainAddress(addr address) bool {
173	return k.domainAddress == addr
174}
175
176// IsWriteAuthorized checks if the caller has write permission
177func (k *kvStore) IsWriteAuthorized(caller address) bool {
178	if k.IsDomainAddress(caller) {
179		return true
180	}
181
182	if !k.isRegisteredAuthorizedCaller(caller) {
183		return false
184	}
185
186	return k.authorizedCallers[caller] >= Write
187}
188
189// GetAuthorizedCallers returns a copy of the authorized callers map with their permissions.
190// A copy is returned so callers cannot mutate the ACL map directly.
191func (k *kvStore) GetAuthorizedCallers() (map[address]Permission, error) {
192	if k.authorizedCallers == nil {
193		return make(map[address]Permission), errors.New(ErrAuthorizedCallerNotFound)
194	}
195
196	out := make(map[address]Permission, len(k.authorizedCallers))
197	for addr, perm := range k.authorizedCallers {
198		out[addr] = perm
199	}
200	return out, nil
201}
202
203// AddAuthorizedCaller adds a new authorized caller with the specified permission
204func (k *kvStore) AddAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
205	if !rlm.IsCurrent() {
206		return errors.New(ErrSpoofedRealm)
207	}
208
209	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
210		return errors.New(ErrUpdatePermissionDenied)
211	}
212
213	if k.isRegisteredAuthorizedCaller(caller) {
214		return errors.New(ErrAuthorizedCallerAlreadyRegistered)
215	}
216
217	if !isValidPermission(permission) {
218		return errors.New(ErrInvalidPermission)
219	}
220
221	k.authorizedCallers[caller] = permission
222
223	return nil
224}
225
226// UpdateAuthorizedCaller updates the permission of an existing authorized caller
227func (k *kvStore) UpdateAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
228	if !rlm.IsCurrent() {
229		return errors.New(ErrSpoofedRealm)
230	}
231
232	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
233		return errors.New(ErrUpdatePermissionDenied)
234	}
235
236	if !k.isRegisteredAuthorizedCaller(caller) {
237		return errors.New(ErrAuthorizedCallerNotFound)
238	}
239
240	if !isValidPermission(permission) {
241		return errors.New(ErrInvalidPermission)
242	}
243
244	k.authorizedCallers[caller] = permission
245
246	return nil
247}
248
249// RemoveAuthorizedCaller removes an authorized caller
250func (k *kvStore) RemoveAuthorizedCaller(_ int, rlm realm, caller address) error {
251	if !rlm.IsCurrent() {
252		return errors.New(ErrSpoofedRealm)
253	}
254
255	if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
256		return errors.New(ErrUpdatePermissionDenied)
257	}
258
259	if !k.isRegisteredAuthorizedCaller(caller) {
260		return errors.New(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(currentRealmAddress address) bool {
277	return currentRealmAddress == 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 Write is assignable via registration APIs.
286// The zero value is rejected so callers must spell out an explicit permission.
287func isValidPermission(permission Permission) bool {
288	return permission == Write
289}