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}