Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}