store.gno
20.77 Kb · 648 lines
1package governance
2
3import (
4 "errors"
5 "strconv"
6
7 "gno.land/p/gnoswap/store/v1"
8 bptree "gno.land/p/nt/bptree/v0"
9 ufmt "gno.land/p/nt/ufmt/v0"
10)
11
12type StoreKey string
13
14// String returns the storage-key text represented by s.
15//
16// Returns:
17// - string: underlying string used as the KV-store key
18func (s StoreKey) String() string {
19 return string(s)
20}
21
22const (
23 StoreKeyConfigCounter StoreKey = "configCounter" // Config version counter
24 StoreKeyProposalCounter StoreKey = "proposalCounter" // Proposal ID counter
25
26 StoreKeyConfigs StoreKey = "configs" // Configurations BPTree
27
28 StoreKeyProposals StoreKey = "proposals" // Proposals BPTree
29
30 StoreKeyActiveProposalsBySnapshot StoreKey = "activeProposalsBySnapshot"
31
32 StoreKeyProposalUserVotingInfos StoreKey = "proposalUserVotingInfos" // Proposal voting infos BPTree
33
34 StoreKeyUserProposals StoreKey = "userProposals" // User proposals mapping BPTree
35)
36
37type governanceStore struct {
38 kvStore store.KVStore
39}
40
41// Counter methods
42// HasConfigCounterStoreKey reports whether the configuration-version counter
43// is present in the KV store.
44//
45// Returns:
46// - bool: true when the configuration counter storage key exists
47func (s *governanceStore) HasConfigCounterStoreKey() bool {
48 return s.kvStore.Has(StoreKeyConfigCounter.String())
49}
50
51// GetConfigCounter retrieves the persisted configuration-version counter.
52// Storage or type-cast failures panic.
53//
54// Returns:
55// - *Counter: counter stored under the configuration counter key
56func (s *governanceStore) GetConfigCounter() *Counter {
57 result, err := s.kvStore.Get(StoreKeyConfigCounter.String())
58 if err != nil {
59 panic(err)
60 }
61
62 counter, ok := result.(*Counter)
63 if !ok {
64 panic(ufmt.Sprintf("failed to cast result to *Counter: %T", result))
65 }
66
67 return counter
68}
69
70// SetConfigCounter persists the configuration-version counter after checking
71// the supplied realm context.
72//
73// Parameters:
74// - _: leading integer discriminator for the authorized store call; callers pass 0
75// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
76// - counter: counter value to persist under the configuration counter key
77//
78// Returns:
79// - error: nil when the counter is persisted; ErrSpoofedRealm or the KV-store write error otherwise
80func (s *governanceStore) SetConfigCounter(_ int, rlm realm, counter *Counter) error {
81 if !rlm.IsCurrent() {
82 return errors.New(ErrSpoofedRealm)
83 }
84
85 return s.kvStore.Set(0, rlm, StoreKeyConfigCounter.String(), counter)
86}
87
88// HasProposalCounterStoreKey reports whether the proposal-ID counter is
89// present in the KV store.
90//
91// Returns:
92// - bool: true when the proposal counter storage key exists
93func (s *governanceStore) HasProposalCounterStoreKey() bool {
94 return s.kvStore.Has(StoreKeyProposalCounter.String())
95}
96
97// GetProposalCounter retrieves the persisted proposal-ID counter.
98// Storage or type-cast failures panic.
99//
100// Returns:
101// - *Counter: counter stored under the proposal counter key
102func (s *governanceStore) GetProposalCounter() *Counter {
103 result, err := s.kvStore.Get(StoreKeyProposalCounter.String())
104 if err != nil {
105 panic(err)
106 }
107
108 counter, ok := result.(*Counter)
109 if !ok {
110 panic(ufmt.Sprintf("failed to cast result to int64: %T", result))
111 }
112
113 return counter
114}
115
116// SetProposalCounter persists the proposal-ID counter after checking the
117// supplied realm context.
118//
119// Parameters:
120// - _: leading integer discriminator for the authorized store call; callers pass 0
121// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
122// - counter: counter value to persist under the proposal counter key
123//
124// Returns:
125// - error: nil when the counter is persisted; ErrSpoofedRealm or the KV-store write error otherwise
126func (s *governanceStore) SetProposalCounter(_ int, rlm realm, counter *Counter) error {
127 if !rlm.IsCurrent() {
128 return errors.New(ErrSpoofedRealm)
129 }
130
131 return s.kvStore.Set(0, rlm, StoreKeyProposalCounter.String(), counter)
132}
133
134// Configs methods
135// HasConfigsStoreKey reports whether the configurations tree is present in the
136// KV store.
137//
138// Returns:
139// - bool: true when the configurations storage key exists
140func (s *governanceStore) HasConfigsStoreKey() bool {
141 return s.kvStore.Has(StoreKeyConfigs.String())
142}
143
144// GetConfigs retrieves the persisted configurations tree.
145// Storage or type-cast failures panic.
146//
147// Returns:
148// - *bptree.BPTree: tree containing governance configurations keyed by version
149func (s *governanceStore) GetConfigs() *bptree.BPTree {
150 result, err := s.kvStore.Get(StoreKeyConfigs.String())
151 if err != nil {
152 panic(err)
153 }
154
155 configs, ok := result.(*bptree.BPTree)
156 if !ok {
157 panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
158 }
159
160 return configs
161}
162
163// SetConfigs persists the configurations tree after checking the supplied
164// realm context.
165//
166// Parameters:
167// - _: leading integer discriminator for the authorized store call; callers pass 0
168// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
169// - configs: configurations tree to persist
170//
171// Returns:
172// - error: nil when the tree is persisted; ErrSpoofedRealm or the KV-store write error otherwise
173func (s *governanceStore) SetConfigs(_ int, rlm realm, configs *bptree.BPTree) error {
174 if !rlm.IsCurrent() {
175 return errors.New(ErrSpoofedRealm)
176 }
177
178 return s.kvStore.Set(0, rlm, StoreKeyConfigs.String(), configs)
179}
180
181// GetConfig retrieves one governance configuration by version.
182// A missing version returns the zero Config and false.
183//
184// Parameters:
185// - version: configuration version used to form the tree key
186//
187// Returns:
188// - Config: configuration stored for version, or zero Config when absent
189// - bool: true when version was found; false when no configuration is stored
190func (s *governanceStore) GetConfig(version int64) (Config, bool) {
191 configs := s.GetConfigs()
192 result := configs.Get(formatInt64Key(version))
193 if result == nil {
194 return Config{}, false
195 }
196
197 config, ok := result.(Config)
198 if !ok {
199 panic(ufmt.Sprintf("failed to cast result to Config: %T", result))
200 }
201
202 return config, true
203}
204
205// SetConfig stores config under version after checking the supplied realm
206// context and the configurations storage key.
207//
208// Parameters:
209// - _: leading integer discriminator for the authorized store call; callers pass 0
210// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
211// - version: configuration version used as the tree key
212// - config: configuration value to store for version
213//
214// Returns:
215// - error: nil when config is stored; an authorization, missing-key, or KV-store error otherwise
216func (s *governanceStore) SetConfig(_ int, rlm realm, version int64, config Config) error {
217 if !rlm.IsCurrent() {
218 return errors.New(ErrSpoofedRealm)
219 }
220
221 if !s.HasConfigsStoreKey() {
222 return errors.New("configs store key not found")
223 }
224
225 configs := s.GetConfigs()
226 configs.Set(formatInt64Key(version), config)
227
228 return s.kvStore.Set(0, rlm, StoreKeyConfigs.String(), configs)
229}
230
231// Proposals methods
232// HasProposalsStoreKey reports whether the proposals tree is present in the KV
233// store.
234//
235// Returns:
236// - bool: true when the proposals storage key exists
237func (s *governanceStore) HasProposalsStoreKey() bool {
238 return s.kvStore.Has(StoreKeyProposals.String())
239}
240
241// GetProposals retrieves the persisted proposals tree.
242// Storage or type-cast failures panic.
243//
244// Returns:
245// - *bptree.BPTree: tree containing proposals keyed by formatted proposal ID
246func (s *governanceStore) GetProposals() *bptree.BPTree {
247 result, err := s.kvStore.Get(StoreKeyProposals.String())
248 if err != nil {
249 panic(err)
250 }
251
252 proposals, ok := result.(*bptree.BPTree)
253 if !ok {
254 panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
255 }
256
257 return proposals
258}
259
260// SetProposals persists the proposals tree after checking the supplied realm
261// context.
262//
263// Parameters:
264// - _: leading integer discriminator for the authorized store call; callers pass 0
265// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
266// - proposals: proposals tree to persist
267//
268// Returns:
269// - error: nil when the tree is persisted; ErrSpoofedRealm or the KV-store write error otherwise
270func (s *governanceStore) SetProposals(_ int, rlm realm, proposals *bptree.BPTree) error {
271 if !rlm.IsCurrent() {
272 return errors.New(ErrSpoofedRealm)
273 }
274
275 return s.kvStore.Set(0, rlm, StoreKeyProposals.String(), proposals)
276}
277
278// GetProposal retrieves a proposal by its identifier.
279// A missing entry or a value of the wrong type returns nil and false.
280//
281// Parameters:
282// - proposalID: proposal identifier used to form the tree key
283//
284// Returns:
285// - *Proposal: stored proposal, or nil when absent or not a Proposal
286// - bool: true when a Proposal was found at proposalID
287func (s *governanceStore) GetProposal(proposalID int64) (*Proposal, bool) {
288 proposals := s.GetProposals()
289 result := proposals.Get(formatInt64Key(proposalID))
290 if result == nil {
291 return nil, false
292 }
293
294 proposal, ok := result.(*Proposal)
295 if !ok {
296 return nil, false
297 }
298 return proposal, true
299}
300
301// SetProposal stores proposal under proposalID after checking realm
302// authorization, storage initialization, and ID consistency.
303//
304// Parameters:
305// - _: leading integer discriminator for the authorized store call; callers pass 0
306// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
307// - proposalID: storage identifier that must equal proposal.ID()
308// - proposal: proposal value to persist; nil or an ID mismatch returns an error
309//
310// Returns:
311// - error: nil when stored; an authorization, missing-key, ID-mismatch, or KV-store error otherwise
312func (s *governanceStore) SetProposal(_ int, rlm realm, proposalID int64, proposal *Proposal) error {
313 if !rlm.IsCurrent() {
314 return errors.New(ErrSpoofedRealm)
315 }
316
317 if !s.HasProposalsStoreKey() {
318 return errors.New("proposals store key not found")
319 }
320
321 if proposal == nil || proposal.ID() != proposalID {
322 return errors.New("proposal ID does not match the storage key")
323 }
324
325 proposals := s.GetProposals()
326 proposals.Set(formatInt64Key(proposalID), proposal)
327
328 return s.kvStore.Set(0, rlm, StoreKeyProposals.String(), proposals)
329}
330
331// HasActiveProposalsBySnapshotStoreKey reports whether the active-proposal
332// snapshot index is present in the KV store.
333//
334// Returns:
335// - bool: true when the active snapshot index storage key exists
336func (s *governanceStore) HasActiveProposalsBySnapshotStoreKey() bool {
337 return s.kvStore.Has(StoreKeyActiveProposalsBySnapshot.String())
338}
339
340// GetActiveProposalsBySnapshot retrieves the active-proposal snapshot index.
341// Storage or type-cast failures panic.
342//
343// Returns:
344// - *bptree.BPTree: tree indexing active proposals by snapshot key
345func (s *governanceStore) GetActiveProposalsBySnapshot() *bptree.BPTree {
346 result, err := s.kvStore.Get(StoreKeyActiveProposalsBySnapshot.String())
347 if err != nil {
348 panic(err)
349 }
350 tree, ok := result.(*bptree.BPTree)
351 if !ok {
352 panic(ufmt.Sprintf("failed to cast active proposal snapshot index: %T", result))
353 }
354 return tree
355}
356
357// SetActiveProposalsBySnapshot persists the active-proposal snapshot index
358// after checking the supplied realm context.
359//
360// Parameters:
361// - _: leading integer discriminator for the authorized store call; callers pass 0
362// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
363// - tree: active-proposal snapshot index tree to persist
364//
365// Returns:
366// - error: nil when the index is persisted; ErrSpoofedRealm or the KV-store write error otherwise
367func (s *governanceStore) SetActiveProposalsBySnapshot(_ int, rlm realm, tree *bptree.BPTree) error {
368 if !rlm.IsCurrent() {
369 return errors.New(ErrSpoofedRealm)
370 }
371 return s.kvStore.Set(0, rlm, StoreKeyActiveProposalsBySnapshot.String(), tree)
372}
373
374// Proposal User Voting Infos methods
375// HasProposalUserVotingInfosStoreKey reports whether the proposal voting-info
376// index is present in the KV store.
377//
378// Returns:
379// - bool: true when the proposal voting-info storage key exists
380func (s *governanceStore) HasProposalUserVotingInfosStoreKey() bool {
381 return s.kvStore.Has(StoreKeyProposalUserVotingInfos.String())
382}
383
384// GetProposalUserVotingInfos retrieves the persisted proposal voting-info
385// index tree. Storage or type-cast failures panic.
386//
387// Returns:
388// - *bptree.BPTree: tree mapping proposal IDs to per-user voting-info trees
389func (s *governanceStore) GetProposalUserVotingInfos() *bptree.BPTree {
390 result, err := s.kvStore.Get(StoreKeyProposalUserVotingInfos.String())
391 if err != nil {
392 panic(err)
393 }
394
395 votingInfos, ok := result.(*bptree.BPTree)
396 if !ok {
397 panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
398 }
399
400 return votingInfos
401}
402
403// SetProposalUserVotingInfos persists the proposal voting-info index after
404// checking the supplied realm context.
405//
406// Parameters:
407// - _: leading integer discriminator for the authorized store call; callers pass 0
408// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
409// - votingInfos: proposal voting-info index tree to persist
410//
411// Returns:
412// - error: nil when the index is persisted; ErrSpoofedRealm or the KV-store write error otherwise
413func (s *governanceStore) SetProposalUserVotingInfos(_ int, rlm realm, votingInfos *bptree.BPTree) error {
414 if !rlm.IsCurrent() {
415 return errors.New(ErrSpoofedRealm)
416 }
417
418 return s.kvStore.Set(0, rlm, StoreKeyProposalUserVotingInfos.String(), votingInfos)
419}
420
421// GetProposalVotingInfos retrieves the per-user voting-info tree for a
422// proposal. A missing entry or wrong type returns nil and false.
423//
424// Parameters:
425// - proposalID: proposal identifier used to select its voting-info tree
426//
427// Returns:
428// - *bptree.BPTree: per-user voting-info tree for proposalID, or nil when absent or invalid
429// - bool: true when a correctly typed voting-info tree was found
430func (s *governanceStore) GetProposalVotingInfos(proposalID int64) (*bptree.BPTree, bool) {
431 votingInfos := s.GetProposalUserVotingInfos()
432 votingInfo := votingInfos.Get(formatInt64Key(proposalID))
433 if votingInfo == nil {
434 return nil, false
435 }
436
437 votingInfoTree, ok := votingInfo.(*bptree.BPTree)
438 if !ok {
439 return nil, false
440 }
441
442 return votingInfoTree, true
443}
444
445// SetProposalVotingInfos stores votingInfos under proposalID after checking
446// realm authorization and index initialization.
447//
448// Parameters:
449// - _: leading integer discriminator for the authorized store call; callers pass 0
450// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
451// - proposalID: proposal identifier used as the nested tree key
452// - votingInfos: per-user voting-info tree to store for proposalID
453//
454// Returns:
455// - error: nil when stored; an authorization, missing-key, or KV-store error otherwise
456func (s *governanceStore) SetProposalVotingInfos(_ int, rlm realm, proposalID int64, votingInfos *bptree.BPTree) error {
457 if !rlm.IsCurrent() {
458 return errors.New(ErrSpoofedRealm)
459 }
460
461 if !s.HasProposalUserVotingInfosStoreKey() {
462 return errors.New("proposal user voting infos store key not found")
463 }
464
465 allVotingInfos := s.GetProposalUserVotingInfos()
466 allVotingInfos.Set(formatInt64Key(proposalID), votingInfos)
467
468 return s.kvStore.Set(0, rlm, StoreKeyProposalUserVotingInfos.String(), allVotingInfos)
469}
470
471// User Proposals methods
472// HasUserProposalsStoreKey reports whether the user-to-proposals index is
473// present in the KV store.
474//
475// Returns:
476// - bool: true when the user proposals storage key exists
477func (s *governanceStore) HasUserProposalsStoreKey() bool {
478 return s.kvStore.Has(StoreKeyUserProposals.String())
479}
480
481// GetUserProposals retrieves the persisted user-to-proposals index.
482// Storage or type-cast failures panic.
483//
484// Returns:
485// - *bptree.BPTree: tree mapping user strings to proposal ID slices
486func (s *governanceStore) GetUserProposals() *bptree.BPTree {
487 result, err := s.kvStore.Get(StoreKeyUserProposals.String())
488 if err != nil {
489 panic(err)
490 }
491
492 userProposals, ok := result.(*bptree.BPTree)
493 if !ok {
494 panic(ufmt.Sprintf("failed to cast result to *bptree.BPTree: %T", result))
495 }
496
497 return userProposals
498}
499
500// SetUserProposals persists the user-to-proposals index after checking the
501// supplied realm context.
502//
503// Parameters:
504// - _: leading integer discriminator for the authorized store call; callers pass 0
505// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
506// - userProposals: user-to-proposals index tree to persist
507//
508// Returns:
509// - error: nil when the index is persisted; ErrSpoofedRealm or the KV-store write error otherwise
510func (s *governanceStore) SetUserProposals(_ int, rlm realm, userProposals *bptree.BPTree) error {
511 if !rlm.IsCurrent() {
512 return errors.New(ErrSpoofedRealm)
513 }
514
515 return s.kvStore.Set(0, rlm, StoreKeyUserProposals.String(), userProposals)
516}
517
518// GetUserProposalIDs retrieves the proposal IDs indexed for user.
519// A missing user returns nil and false; a wrong stored type panics.
520//
521// Parameters:
522// - user: user-string key in the user-to-proposals index
523//
524// Returns:
525// - []int64: proposal IDs currently indexed for user
526// - bool: true when user has an index entry; false when user is absent
527func (s *governanceStore) GetUserProposalIDs(user string) ([]int64, bool) {
528 userProposals := s.GetUserProposals()
529 result := userProposals.Get(user)
530 if result == nil {
531 return nil, false
532 }
533
534 proposalIDs, ok := result.([]int64)
535 if !ok {
536 panic(ufmt.Sprintf("failed to cast result to []int64: %T", result))
537 }
538
539 return proposalIDs, true
540}
541
542// AddUserProposal appends proposalID to the IDs indexed for user and persists
543// the updated user-to-proposals tree.
544//
545// Parameters:
546// - _: leading integer discriminator for the authorized store call; callers pass 0
547// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
548// - user: user-string key whose proposal list is updated
549// - proposalID: proposal identifier to append to user's list
550//
551// Returns:
552// - error: nil when the list is persisted; an authorization, missing-key, or KV-store error otherwise
553func (s *governanceStore) AddUserProposal(_ int, rlm realm, user string, proposalID int64) error {
554 if !rlm.IsCurrent() {
555 return errors.New(ErrSpoofedRealm)
556 }
557
558 if !s.HasUserProposalsStoreKey() {
559 return errors.New("user proposals store key not found")
560 }
561
562 userProposals := s.GetUserProposals()
563
564 // Get existing proposals for user
565 var proposalIDs []int64
566 if existing := userProposals.Get(user); existing != nil {
567 var ok bool
568 proposalIDs, ok = existing.([]int64)
569 if !ok {
570 panic(ufmt.Sprintf("failed to cast result to []int64: %T", existing))
571 }
572 }
573
574 // Add new proposal ID
575 proposalIDs = append(proposalIDs, proposalID)
576 userProposals.Set(user, proposalIDs)
577
578 return s.kvStore.Set(0, rlm, StoreKeyUserProposals.String(), userProposals)
579}
580
581// RemoveUserProposal removes every occurrence of proposalID from user's
582// indexed proposal IDs and persists the resulting tree. Missing users are a
583// no-op.
584//
585// Parameters:
586// - _: leading integer discriminator for the authorized store call; callers pass 0
587// - rlm: propagated realm context; it must satisfy rlm.IsCurrent() or ErrSpoofedRealm is returned
588// - user: user-string key whose proposal list is updated
589// - proposalID: proposal identifier to remove from the user's list
590//
591// Returns:
592// - error: nil when removed or already absent; an authorization, missing-key, or KV-store error otherwise
593func (s *governanceStore) RemoveUserProposal(_ int, rlm realm, user string, proposalID int64) error {
594 if !rlm.IsCurrent() {
595 return errors.New(ErrSpoofedRealm)
596 }
597
598 if !s.HasUserProposalsStoreKey() {
599 return errors.New("user proposals store key not found")
600 }
601
602 userProposals := s.GetUserProposals()
603 proposalIDsRaw := userProposals.Get(user)
604 // proposal already removed
605 if proposalIDsRaw == nil {
606 return nil
607 }
608
609 proposalIDs, ok := proposalIDsRaw.([]int64)
610 if !ok {
611 panic(ufmt.Sprintf("failed to cast result to []int64: %T", proposalIDs))
612 }
613
614 newProposalIDs := make([]int64, 0)
615
616 for _, id := range proposalIDs {
617 if id != proposalID {
618 newProposalIDs = append(newProposalIDs, id)
619 }
620 }
621
622 if len(newProposalIDs) == 0 {
623 userProposals.Remove(user)
624 } else {
625 userProposals.Set(user, newProposalIDs)
626 }
627
628 return s.kvStore.Set(0, rlm, StoreKeyUserProposals.String(), userProposals)
629}
630
631// NewGovernanceStore creates a governance store backed by the provided KV store.
632// The returned interface is used by governance implementations and upgrades.
633//
634// Parameters:
635// - kvStore: KV store used for governance counters, trees, and records
636//
637// Returns:
638// - IGovernanceStore: governance store implementation backed by kvStore
639func NewGovernanceStore(kvStore store.KVStore) IGovernanceStore {
640 return &governanceStore{
641 kvStore: kvStore,
642 }
643}
644
645// formatInt64Key formats int64 identifiers for storage keys.
646func formatInt64Key(id int64) string {
647 return strconv.FormatInt(id, 10)
648}