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

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}