store.gno
32.40 Kb · 931 lines
1package protocol_fee
2
3import (
4 "errors"
5
6 bptree "gno.land/p/nt/bptree/v0"
7 ufmt "gno.land/p/nt/ufmt/v0"
8
9 "gno.land/p/gnoswap/gnsmath/v1"
10 "gno.land/p/gnoswap/store/v1"
11 "gno.land/p/gnoswap/utils/v1"
12)
13
14type StoreKey string
15
16// String returns the textual store-key value.
17//
18// Returns:
19// - key: The string representation of the StoreKey.
20func (s StoreKey) String() string {
21 return string(s)
22}
23
24const (
25 // By default, devOps will get 0% of the protocol fee (which means gov/staker will get 100% of the protocol fee)
26 // This percentage can be modified through governance.
27 StoreKeyDevOpsPct StoreKey = "devOpsPct"
28
29 // accuToGovStaker tracks the cumulative amount allocated to GovStaker,
30 // including allocations that are still pending distribution.
31 StoreKeyAccuToGovStaker StoreKey = "accuToGovStaker" // tokenPath -> amount
32 // accuToDevOps tracks the cumulative amount allocated to DevOps,
33 // including allocations that are still pending distribution.
34 StoreKeyAccuToDevOps StoreKey = "accuToDevOps" // tokenPath -> amount
35
36 // Distribution-history trees track cumulative amounts actually transferred.
37 StoreKeyDistributedToGovStakerHistory StoreKey = "distributedToGovStakerHistory" // tokenPath -> amount
38 StoreKeyDistributedToDevOpsHistory StoreKey = "distributedToDevOpsHistory" // tokenPath -> amount
39
40 // reservedTokens tracks token paths collected but not yet distributed.
41 StoreKeyReservedTokens StoreKey = "reservedTokens"
42
43 // accrualEpoch, accrualBuckets and accrualPendingTokens track the gov/staker share
44 // per token path and accrual epoch until gov/staker folds it into its accumulator.
45 StoreKeyAccrualEpoch StoreKey = "accrualEpoch"
46 StoreKeyAccrualBuckets StoreKey = "accrualBuckets" // tokenPath -> (epoch -> amount)
47 StoreKeyAccrualPendingTokens StoreKey = "accrualPendingTokens" // tokenPath -> true
48)
49
50const (
51 defaultDevOpsPct = int64(0)
52 errSpoofedRealm = "rlm does not match the current crossing frame"
53)
54
55// NewBPTreeN allocates a BP-tree under /r/gnoswap/protocol_fee's realm context
56// so tree.Set leaf-slot writes clear the readonly-taint gate regardless of
57// which realm (protocol_fee/v1, mock, tests) calls Set. Callers must allocate
58// protocol_fee trees through here rather than bptree.NewBPTreeN directly.
59//
60// Parameters:
61// - fanout: Branching factor passed to the BP-tree constructor.
62//
63// Returns:
64// - tree: A new BP-tree configured with fanout and allocated in the protocol-fee realm context.
65func NewBPTreeN(fanout int) *bptree.BPTree {
66 return bptree.NewBPTreeN(fanout)
67}
68
69type protocolFeeStore struct {
70 kvStore store.KVStore
71}
72
73// handle devOpsPct store data
74//
75// Returns:
76// - exists: True when the DevOps percentage store key is present in the KV store.
77func (s *protocolFeeStore) HasDevOpsPctStoreKey() bool {
78 return s.kvStore.Has(StoreKeyDevOpsPct.String())
79}
80
81// InitializeDevOpsPct creates the DevOps allocation percentage entry with its default value.
82//
83// Parameters:
84// - _: Crossing discriminator for the store operation; pass 0.
85// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
86//
87// Returns:
88// - err: Nil when the key is initialized; otherwise the current-realm or KV-store write error.
89func (s *protocolFeeStore) InitializeDevOpsPct(_ int, rlm realm) error {
90 if !rlm.IsCurrent() {
91 return errors.New(errSpoofedRealm)
92 }
93
94 return s.kvStore.Set(0, rlm, StoreKeyDevOpsPct.String(), defaultDevOpsPct)
95}
96
97// GetDevOpsPct reads the persisted DevOps allocation percentage.
98//
99// Returns:
100// - pct: The stored DevOps allocation percentage in basis points; the method panics if the key is missing or cannot be decoded as int64.
101func (s *protocolFeeStore) GetDevOpsPct() int64 {
102 devOpsPct, err := s.kvStore.GetInt64(StoreKeyDevOpsPct.String())
103 if err != nil {
104 panic(err)
105 }
106
107 return devOpsPct
108}
109
110// SetDevOpsPct persists the DevOps allocation percentage.
111//
112// Parameters:
113// - _: Crossing discriminator for the store operation; pass 0.
114// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
115// - pct: DevOps share of protocol fees in basis points; the state layer constrains this value to 0 through 10000 before calling the store.
116//
117// Returns:
118// - err: Nil when the percentage is persisted; otherwise the current-realm or KV-store write error.
119func (s *protocolFeeStore) SetDevOpsPct(_ int, rlm realm, pct int64) error {
120 if !rlm.IsCurrent() {
121 return errors.New(errSpoofedRealm)
122 }
123
124 return s.kvStore.Set(0, rlm, StoreKeyDevOpsPct.String(), pct)
125}
126
127// handle accuToGovStaker store data
128//
129// Returns:
130// - exists: True when the cumulative Gov/Staker allocation tree key is present in the KV store.
131func (s *protocolFeeStore) HasAccuToGovStakerStoreKey() bool {
132 return s.kvStore.Has(StoreKeyAccuToGovStaker.String())
133}
134
135// InitializeAccuToGovStaker creates the cumulative Gov/Staker allocation tree.
136//
137// Parameters:
138// - _: Crossing discriminator for the store operation; pass 0.
139// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
140//
141// Returns:
142// - err: Nil when the tree is initialized; otherwise the current-realm or KV-store write error.
143func (s *protocolFeeStore) InitializeAccuToGovStaker(_ int, rlm realm) error {
144 if !rlm.IsCurrent() {
145 return errors.New(errSpoofedRealm)
146 }
147
148 return s.kvStore.Set(0, rlm, StoreKeyAccuToGovStaker.String(), NewBPTreeN(16))
149}
150
151// GetAccuToGovStaker reads the cumulative Gov/Staker allocation tree.
152//
153// Returns:
154// - tree: The BP-tree mapping token paths to cumulative amounts; the method panics if the store value is absent or has the wrong type.
155func (s *protocolFeeStore) GetAccuToGovStaker() *bptree.BPTree {
156 accuToGovStaker, err := s.kvStore.GetBPTree(StoreKeyAccuToGovStaker.String())
157 if err != nil {
158 panic(err)
159 }
160
161 return accuToGovStaker
162}
163
164// GetAccuToGovStakerItem reads one token's cumulative Gov/Staker allocation.
165//
166// Parameters:
167// - tokenPath: Token path whose cumulative allocation should be looked up.
168//
169// Returns:
170// - amount: The stored cumulative allocation for tokenPath, or zero when no entry exists.
171// - found: True when tokenPath has an entry in the allocation tree; false when it is absent.
172func (s *protocolFeeStore) GetAccuToGovStakerItem(tokenPath string) (int64, bool) {
173 accuToGovStaker, err := s.kvStore.GetBPTree(StoreKeyAccuToGovStaker.String())
174 if err != nil {
175 panic(err)
176 }
177
178 result := accuToGovStaker.Get(tokenPath)
179 if result == nil {
180 return 0, false
181 }
182
183 amount, ok := result.(int64)
184 if !ok {
185 panic(ufmt.Errorf("failed to cast result to int64: %T", result))
186 }
187
188 return amount, true
189}
190
191// SetAccuToGovStakerItem updates one token's cumulative Gov/Staker allocation.
192//
193// Parameters:
194// - _: Crossing discriminator for the store operation; pass 0.
195// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
196// - tokenPath: Token path whose cumulative allocation is replaced.
197// - amount: Cumulative Gov/Staker allocation to store for tokenPath.
198//
199// Returns:
200// - err: Nil when the allocation tree is persisted; otherwise the current-realm, tree-read, or KV-store write error.
201func (s *protocolFeeStore) SetAccuToGovStakerItem(_ int, rlm realm, tokenPath string, amount int64) error {
202 if !rlm.IsCurrent() {
203 return errors.New(errSpoofedRealm)
204 }
205
206 accuToGovStaker, err := s.kvStore.GetBPTree(StoreKeyAccuToGovStaker.String())
207 if err != nil {
208 return err
209 }
210
211 accuToGovStaker.Set(tokenPath, amount)
212
213 return s.kvStore.Set(0, rlm, StoreKeyAccuToGovStaker.String(), accuToGovStaker)
214}
215
216// handle accuToDevOps store data
217//
218// Returns:
219// - exists: True when the cumulative DevOps allocation tree key is present in the KV store.
220func (s *protocolFeeStore) HasAccuToDevOpsStoreKey() bool {
221 return s.kvStore.Has(StoreKeyAccuToDevOps.String())
222}
223
224// InitializeAccuToDevOps creates the cumulative DevOps allocation tree.
225//
226// Parameters:
227// - _: Crossing discriminator for the store operation; pass 0.
228// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
229//
230// Returns:
231// - err: Nil when the tree is initialized; otherwise the current-realm or KV-store write error.
232func (s *protocolFeeStore) InitializeAccuToDevOps(_ int, rlm realm) error {
233 if !rlm.IsCurrent() {
234 return errors.New(errSpoofedRealm)
235 }
236
237 return s.kvStore.Set(0, rlm, StoreKeyAccuToDevOps.String(), NewBPTreeN(16))
238}
239
240// GetAccuToDevOps reads the cumulative DevOps allocation tree.
241//
242// Returns:
243// - tree: The BP-tree mapping token paths to cumulative amounts; the method panics if the store value is absent or has the wrong type.
244func (s *protocolFeeStore) GetAccuToDevOps() *bptree.BPTree {
245 accuToDevOps, err := s.kvStore.GetBPTree(StoreKeyAccuToDevOps.String())
246 if err != nil {
247 panic(err)
248 }
249
250 return accuToDevOps
251}
252
253// GetAccuToDevOpsItem reads one token's cumulative DevOps allocation.
254//
255// Parameters:
256// - tokenPath: Token path whose cumulative allocation should be looked up.
257//
258// Returns:
259// - amount: The stored cumulative allocation for tokenPath, or zero when no entry exists.
260// - found: True when tokenPath has an entry in the allocation tree; false when it is absent.
261func (s *protocolFeeStore) GetAccuToDevOpsItem(tokenPath string) (int64, bool) {
262 accuToDevOps, err := s.kvStore.GetBPTree(StoreKeyAccuToDevOps.String())
263 if err != nil {
264 panic(err)
265 }
266
267 result := accuToDevOps.Get(tokenPath)
268 if result == nil {
269 return 0, false
270 }
271
272 amount, ok := result.(int64)
273 if !ok {
274 panic(ufmt.Errorf("failed to cast result to int64: %T", result))
275 }
276
277 return amount, true
278}
279
280// SetAccuToDevOpsItem updates one token's cumulative DevOps allocation.
281//
282// Parameters:
283// - _: Crossing discriminator for the store operation; pass 0.
284// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
285// - tokenPath: Token path whose cumulative allocation is replaced.
286// - amount: Cumulative DevOps allocation to store for tokenPath.
287//
288// Returns:
289// - err: Nil when the allocation tree is persisted; otherwise the current-realm, tree-read, or KV-store write error.
290func (s *protocolFeeStore) SetAccuToDevOpsItem(_ int, rlm realm, tokenPath string, amount int64) error {
291 if !rlm.IsCurrent() {
292 return errors.New(errSpoofedRealm)
293 }
294
295 accuToDevOps, err := s.kvStore.GetBPTree(StoreKeyAccuToDevOps.String())
296 if err != nil {
297 return err
298 }
299
300 accuToDevOps.Set(tokenPath, amount)
301
302 return s.kvStore.Set(0, rlm, StoreKeyAccuToDevOps.String(), accuToDevOps)
303}
304
305// handle distributedToGovStakerHistory store data
306// HasDistributedToGovStakerHistoryStoreKey reports whether the Gov/Staker distribution-history tree exists.
307//
308// Returns:
309// - exists: True when the Gov/Staker distribution-history tree key is present in the KV store.
310func (s *protocolFeeStore) HasDistributedToGovStakerHistoryStoreKey() bool {
311 return s.kvStore.Has(StoreKeyDistributedToGovStakerHistory.String())
312}
313
314// InitializeDistributedToGovStakerHistory creates the Gov/Staker distribution-history tree.
315//
316// Parameters:
317// - _: Crossing discriminator for the store operation; pass 0.
318// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
319//
320// Returns:
321// - err: Nil when the history tree is initialized; otherwise the current-realm or KV-store write error.
322func (s *protocolFeeStore) InitializeDistributedToGovStakerHistory(_ int, rlm realm) error {
323 if !rlm.IsCurrent() {
324 return errors.New(errSpoofedRealm)
325 }
326
327 return s.kvStore.Set(0, rlm, StoreKeyDistributedToGovStakerHistory.String(), NewBPTreeN(16))
328}
329
330// GetDistributedToGovStakerHistory reads the cumulative Gov/Staker distribution-history tree.
331//
332// Returns:
333// - tree: The BP-tree mapping token paths to amounts actually distributed to Gov/Staker; the method panics if the stored value is absent or has the wrong type.
334func (s *protocolFeeStore) GetDistributedToGovStakerHistory() *bptree.BPTree {
335 distributedToGovStakerHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToGovStakerHistory.String())
336 if err != nil {
337 panic(err)
338 }
339
340 return distributedToGovStakerHistory
341}
342
343// GetDistributedToGovStakerHistoryItem reads one token's cumulative Gov/Staker distribution history.
344//
345// Parameters:
346// - tokenPath: Token path whose distributed amount should be looked up.
347//
348// Returns:
349// - amount: The stored cumulative amount distributed to Gov/Staker for tokenPath, or zero when no entry exists.
350// - found: True when tokenPath has a history entry; false when it is absent.
351func (s *protocolFeeStore) GetDistributedToGovStakerHistoryItem(tokenPath string) (int64, bool) {
352 distributedToGovStakerHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToGovStakerHistory.String())
353 if err != nil {
354 panic(err)
355 }
356
357 result := distributedToGovStakerHistory.Get(tokenPath)
358 if result == nil {
359 return 0, false
360 }
361
362 amount, ok := result.(int64)
363 if !ok {
364 panic(ufmt.Errorf("failed to cast result to int64: %T", result))
365 }
366
367 return amount, true
368}
369
370// SetDistributedToGovStakerHistoryItem updates one token's cumulative Gov/Staker distribution history.
371//
372// Parameters:
373// - _: Crossing discriminator for the store operation; pass 0.
374// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
375// - tokenPath: Token path whose distributed amount is replaced.
376// - amount: Cumulative amount actually distributed to Gov/Staker to store for tokenPath.
377//
378// Returns:
379// - err: Nil when the history tree is persisted; otherwise the current-realm, tree-read, or KV-store write error.
380func (s *protocolFeeStore) SetDistributedToGovStakerHistoryItem(_ int, rlm realm, tokenPath string, amount int64) error {
381 if !rlm.IsCurrent() {
382 return errors.New(errSpoofedRealm)
383 }
384
385 distributedToGovStakerHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToGovStakerHistory.String())
386 if err != nil {
387 return err
388 }
389
390 distributedToGovStakerHistory.Set(tokenPath, amount)
391
392 return s.kvStore.Set(0, rlm, StoreKeyDistributedToGovStakerHistory.String(), distributedToGovStakerHistory)
393}
394
395// HasDistributedToDevOpsHistoryStoreKey reports whether the DevOps distribution-history tree exists.
396//
397// Returns:
398// - exists: True when the DevOps distribution-history tree key is present in the KV store.
399//
400// handle distributedToDevOpsHistory store data
401func (s *protocolFeeStore) HasDistributedToDevOpsHistoryStoreKey() bool {
402 return s.kvStore.Has(StoreKeyDistributedToDevOpsHistory.String())
403}
404
405// InitializeDistributedToDevOpsHistory creates the DevOps distribution-history tree.
406//
407// Parameters:
408// - _: Crossing discriminator for the store operation; pass 0.
409// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
410//
411// Returns:
412// - err: Nil when the history tree is initialized; otherwise the current-realm or KV-store write error.
413func (s *protocolFeeStore) InitializeDistributedToDevOpsHistory(_ int, rlm realm) error {
414 if !rlm.IsCurrent() {
415 return errors.New(errSpoofedRealm)
416 }
417
418 return s.kvStore.Set(0, rlm, StoreKeyDistributedToDevOpsHistory.String(), NewBPTreeN(16))
419}
420
421// GetDistributedToDevOpsHistory reads the cumulative DevOps distribution-history tree.
422//
423// Returns:
424// - tree: The BP-tree mapping token paths to amounts actually distributed to DevOps; the method panics if the stored value is absent or has the wrong type.
425func (s *protocolFeeStore) GetDistributedToDevOpsHistory() *bptree.BPTree {
426 distributedToDevOpsHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToDevOpsHistory.String())
427 if err != nil {
428 panic(err)
429 }
430
431 return distributedToDevOpsHistory
432}
433
434// GetDistributedToDevOpsHistoryItem reads one token's cumulative DevOps distribution history.
435//
436// Parameters:
437// - tokenPath: Token path whose distributed amount should be looked up.
438//
439// Returns:
440// - amount: The stored cumulative amount distributed to DevOps for tokenPath, or zero when no entry exists.
441// - found: True when tokenPath has a history entry; false when it is absent.
442func (s *protocolFeeStore) GetDistributedToDevOpsHistoryItem(tokenPath string) (int64, bool) {
443 distributedToDevOpsHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToDevOpsHistory.String())
444 if err != nil {
445 panic(err)
446 }
447
448 result := distributedToDevOpsHistory.Get(tokenPath)
449 if result == nil {
450 return 0, false
451 }
452
453 amount, ok := result.(int64)
454 if !ok {
455 panic(ufmt.Errorf("failed to cast result to int64: %T", result))
456 }
457
458 return amount, true
459}
460
461// SetDistributedToDevOpsHistoryItem updates one token's cumulative DevOps distribution history.
462//
463// Parameters:
464// - _: Crossing discriminator for the store operation; pass 0.
465// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
466// - tokenPath: Token path whose distributed amount is replaced.
467// - amount: Cumulative amount actually distributed to DevOps to store for tokenPath.
468//
469// Returns:
470// - err: Nil when the history tree is persisted; otherwise the current-realm, tree-read, or KV-store write error.
471func (s *protocolFeeStore) SetDistributedToDevOpsHistoryItem(_ int, rlm realm, tokenPath string, amount int64) error {
472 if !rlm.IsCurrent() {
473 return errors.New(errSpoofedRealm)
474 }
475
476 distributedToDevOpsHistory, err := s.kvStore.GetBPTree(StoreKeyDistributedToDevOpsHistory.String())
477 if err != nil {
478 return err
479 }
480
481 distributedToDevOpsHistory.Set(tokenPath, amount)
482
483 return s.kvStore.Set(0, rlm, StoreKeyDistributedToDevOpsHistory.String(), distributedToDevOpsHistory)
484}
485
486// handle reservedTokens store data
487//
488// reservedTokens is the set of token paths that collected a fee not yet transferred out
489// by DistributeProtocolFee. It is kept as a tree so that a single token can be added or
490// settled without rewriting the whole set.
491//
492// Returns:
493// - exists: True when the reserved-token index key is present in the KV store.
494func (s *protocolFeeStore) HasReservedTokensStoreKey() bool {
495 return s.kvStore.Has(StoreKeyReservedTokens.String())
496}
497
498// InitializeReservedTokens creates the reserved-token index tree.
499//
500// Parameters:
501// - _: Crossing discriminator for the store operation; pass 0.
502// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
503//
504// Returns:
505// - err: Nil when the index tree is initialized; otherwise the current-realm or KV-store write error.
506func (s *protocolFeeStore) InitializeReservedTokens(_ int, rlm realm) error {
507 if !rlm.IsCurrent() {
508 return errors.New(errSpoofedRealm)
509 }
510
511 return s.kvStore.Set(0, rlm, StoreKeyReservedTokens.String(), NewBPTreeN(16))
512}
513
514func (s *protocolFeeStore) getReservedTokenTree() *bptree.BPTree {
515 reservedTokens, err := s.kvStore.GetBPTree(StoreKeyReservedTokens.String())
516 if err != nil {
517 panic(err)
518 }
519
520 return reservedTokens
521}
522
523// GetReservedTokens returns all token paths currently reserved for distribution.
524//
525// Returns:
526// - tokenPaths: Token paths present in the reserved-token index, in BP-tree iteration order.
527func (s *protocolFeeStore) GetReservedTokens() []string {
528 return collectTreeKeys(s.getReservedTokenTree())
529}
530
531// HasReservedToken reports whether tokenPath is currently reserved for distribution.
532//
533// Parameters:
534// - tokenPath: Token path to look up in the reserved-token index.
535//
536// Returns:
537// - exists: True when tokenPath is in the reserved-token index; false otherwise.
538func (s *protocolFeeStore) HasReservedToken(tokenPath string) bool {
539 return s.getReservedTokenTree().Has(tokenPath)
540}
541
542// AddReservedToken adds tokenPath to the reserved-token index.
543//
544// Parameters:
545// - _: Crossing discriminator for the store operation; pass 0.
546// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
547// - tokenPath: Token path collected for protocol-fee distribution.
548//
549// Returns:
550// - err: Nil when tokenPath is already present or has been added; otherwise the current-realm or KV-store write error.
551func (s *protocolFeeStore) AddReservedToken(_ int, rlm realm, tokenPath string) error {
552 if !rlm.IsCurrent() {
553 return errors.New(errSpoofedRealm)
554 }
555
556 reservedTokens := s.getReservedTokenTree()
557 if reservedTokens.Has(tokenPath) {
558 return nil
559 }
560
561 reservedTokens.Set(tokenPath, true)
562
563 return s.kvStore.Set(0, rlm, StoreKeyReservedTokens.String(), reservedTokens)
564}
565
566// RemoveReservedToken removes one tokenPath from the reserved-token set when present.
567//
568// Parameters:
569// - _: Crossing discriminator for the store operation; pass 0.
570// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
571// - tokenPath: Token path to remove from the reserved-token set.
572//
573// Returns:
574// - err: Nil when tokenPath is absent or has been removed; otherwise the current-realm or KV-store write error.
575func (s *protocolFeeStore) RemoveReservedToken(_ int, rlm realm, tokenPath string) error {
576 if !rlm.IsCurrent() {
577 return errors.New(errSpoofedRealm)
578 }
579
580 reservedTokens := s.getReservedTokenTree()
581 if _, removed := reservedTokens.Remove(tokenPath); !removed {
582 return nil
583 }
584
585 return s.kvStore.Set(0, rlm, StoreKeyReservedTokens.String(), reservedTokens)
586}
587
588// handle accrualEpoch store data
589//
590// accrualEpoch numbers the intervals between gov/staker stake changes. gov/staker
591// advances it on every stake change, so every fee that arrives is attributed to the
592// stake distribution in force when it arrived.
593//
594// Returns:
595// - exists: True when the accrual-epoch store key is present in the KV store.
596func (s *protocolFeeStore) HasAccrualEpochStoreKey() bool {
597 return s.kvStore.Has(StoreKeyAccrualEpoch.String())
598}
599
600// InitializeAccrualEpoch initializes the current fee-accrual epoch to zero.
601//
602// Parameters:
603// - _: Crossing discriminator for the store operation; pass 0.
604// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
605//
606// Returns:
607// - err: Nil when the epoch key is initialized; otherwise the current-realm or KV-store write error.
608func (s *protocolFeeStore) InitializeAccrualEpoch(_ int, rlm realm) error {
609 if !rlm.IsCurrent() {
610 return errors.New(errSpoofedRealm)
611 }
612
613 return s.kvStore.Set(0, rlm, StoreKeyAccrualEpoch.String(), int64(0))
614}
615
616// GetAccrualEpoch reads the epoch under which new Gov/Staker accrual buckets are recorded.
617//
618// Returns:
619// - epoch: The persisted current accrual epoch; the method panics if the key is missing or cannot be decoded as int64.
620func (s *protocolFeeStore) GetAccrualEpoch() int64 {
621 accrualEpoch, err := s.kvStore.GetInt64(StoreKeyAccrualEpoch.String())
622 if err != nil {
623 panic(err)
624 }
625
626 return accrualEpoch
627}
628
629// SetAccrualEpoch persists the current fee-accrual epoch.
630//
631// Parameters:
632// - _: Crossing discriminator for the store operation; pass 0.
633// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
634// - accrualEpoch: Epoch number assigned to newly collected Gov/Staker accrual buckets.
635//
636// Returns:
637// - err: Nil when the epoch is persisted; otherwise the current-realm or KV-store write error.
638func (s *protocolFeeStore) SetAccrualEpoch(_ int, rlm realm, accrualEpoch int64) error {
639 if !rlm.IsCurrent() {
640 return errors.New(errSpoofedRealm)
641 }
642
643 return s.kvStore.Set(0, rlm, StoreKeyAccrualEpoch.String(), accrualEpoch)
644}
645
646// handle accrualBuckets store data
647//
648// accrualBuckets holds, per token path, the gov/staker share collected during each
649// accrual epoch that gov/staker has not folded into its accumulator yet. Folding
650// consumes the oldest buckets first, so a token is settled in epoch order.
651//
652// Returns:
653// - exists: True when the accrual-bucket store key is present in the KV store.
654func (s *protocolFeeStore) HasAccrualBucketsStoreKey() bool {
655 return s.kvStore.Has(StoreKeyAccrualBuckets.String())
656}
657
658// InitializeAccrualBuckets creates the nested accrual-bucket tree.
659//
660// Parameters:
661// - _: Crossing discriminator for the store operation; pass 0.
662// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
663//
664// Returns:
665// - err: Nil when the bucket tree is initialized; otherwise the current-realm or KV-store write error.
666func (s *protocolFeeStore) InitializeAccrualBuckets(_ int, rlm realm) error {
667 if !rlm.IsCurrent() {
668 return errors.New(errSpoofedRealm)
669 }
670
671 return s.kvStore.Set(0, rlm, StoreKeyAccrualBuckets.String(), NewBPTreeN(16))
672}
673
674func (s *protocolFeeStore) getAccrualBucketTree() *bptree.BPTree {
675 accrualBuckets, err := s.kvStore.GetBPTree(StoreKeyAccrualBuckets.String())
676 if err != nil {
677 panic(err)
678 }
679
680 return accrualBuckets
681}
682
683func (s *protocolFeeStore) getTokenAccrualBucketTree(accrualBuckets *bptree.BPTree, tokenPath string) *bptree.BPTree {
684 result := accrualBuckets.Get(tokenPath)
685 if result == nil {
686 return nil
687 }
688
689 tokenBuckets, ok := result.(*bptree.BPTree)
690 if !ok {
691 panic(ufmt.Errorf("failed to cast result to *bptree.BPTree: %T", result))
692 }
693
694 return tokenBuckets
695}
696
697// GetAccrualBuckets returns up to limit of the oldest pending buckets of tokenPath in
698// epoch order. A limit of zero or less returns every pending bucket.
699//
700// Parameters:
701// - tokenPath: Token path whose pending Gov/Staker accrual buckets should be read.
702// - limit: Maximum number of buckets to return; zero or a negative value returns all pending buckets.
703//
704// Returns:
705// - epochs: Epoch numbers for the returned buckets, in ascending BP-tree iteration order.
706// - amounts: Gov/Staker amounts for the returned epochs; amounts[i] corresponds to epochs[i].
707func (s *protocolFeeStore) GetAccrualBuckets(tokenPath string, limit int) ([]int64, []int64) {
708 epochs := []int64{}
709 amounts := []int64{}
710
711 tokenBuckets := s.getTokenAccrualBucketTree(s.getAccrualBucketTree(), tokenPath)
712 if tokenBuckets == nil {
713 return epochs, amounts
714 }
715
716 tokenBuckets.Iterate("", "", func(key string, value any) bool {
717 if limit > 0 && len(epochs) >= limit {
718 return true
719 }
720
721 amount, ok := value.(int64)
722 if !ok {
723 panic(ufmt.Errorf("failed to cast result to int64: %T", value))
724 }
725
726 epochs = append(epochs, decodeEpochKey(key))
727 amounts = append(amounts, amount)
728
729 return false
730 })
731
732 return epochs, amounts
733}
734
735// AddAccrualBucket adds amount to the bucket of tokenPath at epoch.
736//
737// Parameters:
738// - _: Crossing discriminator for the store operation; pass 0.
739// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
740// - tokenPath: Token path whose Gov/Staker accrual bucket is updated.
741// - epoch: Non-negative accrual epoch identifying the bucket; negative epochs panic during key encoding.
742// - amount: Amount to add to the existing bucket value.
743//
744// Returns:
745// - err: Nil when the bucket tree is persisted; otherwise the current-realm or KV-store write error.
746func (s *protocolFeeStore) AddAccrualBucket(_ int, rlm realm, tokenPath string, epoch int64, amount int64) error {
747 if !rlm.IsCurrent() {
748 return errors.New(errSpoofedRealm)
749 }
750
751 accrualBuckets := s.getAccrualBucketTree()
752 tokenBuckets := s.getTokenAccrualBucketTree(accrualBuckets, tokenPath)
753 if tokenBuckets == nil {
754 tokenBuckets = NewBPTreeN(16)
755 accrualBuckets.Set(tokenPath, tokenBuckets)
756 }
757
758 key := encodeEpochKey(epoch)
759 existing := int64(0)
760 if result := tokenBuckets.Get(key); result != nil {
761 current, ok := result.(int64)
762 if !ok {
763 panic(ufmt.Errorf("failed to cast result to int64: %T", result))
764 }
765 existing = current
766 }
767
768 tokenBuckets.Set(key, gnsmath.SafeAddInt64(existing, amount))
769
770 return s.kvStore.Set(0, rlm, StoreKeyAccrualBuckets.String(), accrualBuckets)
771}
772
773// RemoveAccrualBuckets drops the buckets of tokenPath at the given epochs. The token's
774// tree is dropped as well once no bucket remains.
775//
776// Parameters:
777// - _: Crossing discriminator for the store operation; pass 0.
778// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
779// - tokenPath: Token path whose selected accrual buckets are removed.
780// - epochs: Epoch numbers whose buckets should be deleted; an absent token or epoch is ignored.
781//
782// Returns:
783// - err: Nil when removals are applied (including an absent token); otherwise the current-realm or KV-store write error.
784func (s *protocolFeeStore) RemoveAccrualBuckets(_ int, rlm realm, tokenPath string, epochs []int64) error {
785 if !rlm.IsCurrent() {
786 return errors.New(errSpoofedRealm)
787 }
788
789 accrualBuckets := s.getAccrualBucketTree()
790 tokenBuckets := s.getTokenAccrualBucketTree(accrualBuckets, tokenPath)
791 if tokenBuckets == nil {
792 return nil
793 }
794
795 for _, epoch := range epochs {
796 tokenBuckets.Remove(encodeEpochKey(epoch))
797 }
798
799 if tokenBuckets.Size() == 0 {
800 accrualBuckets.Remove(tokenPath)
801 }
802
803 return s.kvStore.Set(0, rlm, StoreKeyAccrualBuckets.String(), accrualBuckets)
804}
805
806// handle accrualPendingTokens store data
807//
808// accrualPendingTokens is the set of token paths that still own at least one accrual
809// bucket, so gov/staker can enumerate what it has to fold without scanning every token.
810//
811// Returns:
812// - exists: True when the pending-token index key is present in the KV store.
813func (s *protocolFeeStore) HasAccrualPendingTokensStoreKey() bool {
814 return s.kvStore.Has(StoreKeyAccrualPendingTokens.String())
815}
816
817// InitializeAccrualPendingTokens creates the index of tokens with pending accrual buckets.
818//
819// Parameters:
820// - _: Crossing discriminator for the store operation; pass 0.
821// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
822//
823// Returns:
824// - err: Nil when the pending-token index is initialized; otherwise the current-realm or KV-store write error.
825func (s *protocolFeeStore) InitializeAccrualPendingTokens(_ int, rlm realm) error {
826 if !rlm.IsCurrent() {
827 return errors.New(errSpoofedRealm)
828 }
829
830 return s.kvStore.Set(0, rlm, StoreKeyAccrualPendingTokens.String(), NewBPTreeN(16))
831}
832
833func (s *protocolFeeStore) getAccrualPendingTokenTree() *bptree.BPTree {
834 accrualPendingTokens, err := s.kvStore.GetBPTree(StoreKeyAccrualPendingTokens.String())
835 if err != nil {
836 panic(err)
837 }
838
839 return accrualPendingTokens
840}
841
842// GetAccrualPendingTokens returns token paths that still own accrual buckets.
843//
844// Returns:
845// - tokenPaths: Token paths in the pending-accrual index, in BP-tree iteration order.
846func (s *protocolFeeStore) GetAccrualPendingTokens() []string {
847 return collectTreeKeys(s.getAccrualPendingTokenTree())
848}
849
850// AddAccrualPendingToken adds tokenPath to the pending-accrual index.
851//
852// Parameters:
853// - _: Crossing discriminator for the store operation; pass 0.
854// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
855// - tokenPath: Token path with at least one accrual bucket pending.
856//
857// Returns:
858// - err: Nil when tokenPath is already present or has been added; otherwise the current-realm or KV-store write error.
859func (s *protocolFeeStore) AddAccrualPendingToken(_ int, rlm realm, tokenPath string) error {
860 if !rlm.IsCurrent() {
861 return errors.New(errSpoofedRealm)
862 }
863
864 accrualPendingTokens := s.getAccrualPendingTokenTree()
865 if accrualPendingTokens.Has(tokenPath) {
866 return nil
867 }
868
869 accrualPendingTokens.Set(tokenPath, true)
870
871 return s.kvStore.Set(0, rlm, StoreKeyAccrualPendingTokens.String(), accrualPendingTokens)
872}
873
874// RemoveAccrualPendingToken drops one token from the set. It writes only when the
875// token was listed.
876//
877// Parameters:
878// - _: Crossing discriminator for the store operation; pass 0.
879// - rlm: Propagated realm context for this store write; it must be current (`rlm.IsCurrent()`).
880// - tokenPath: Token path to remove from the pending-accrual index.
881//
882// Returns:
883// - err: Nil when tokenPath is absent or has been removed; otherwise the current-realm or KV-store write error.
884func (s *protocolFeeStore) RemoveAccrualPendingToken(_ int, rlm realm, tokenPath string) error {
885 if !rlm.IsCurrent() {
886 return errors.New(errSpoofedRealm)
887 }
888
889 accrualPendingTokens := s.getAccrualPendingTokenTree()
890 if _, removed := accrualPendingTokens.Remove(tokenPath); !removed {
891 return nil
892 }
893
894 return s.kvStore.Set(0, rlm, StoreKeyAccrualPendingTokens.String(), accrualPendingTokens)
895}
896
897func collectTreeKeys(tree *bptree.BPTree) []string {
898 keys := make([]string, 0, tree.Size())
899 tree.Iterate("", "", func(key string, _ any) bool {
900 keys = append(keys, key)
901 return false
902 })
903
904 return keys
905}
906
907func encodeEpochKey(epoch int64) string {
908 if epoch < 0 {
909 panic(ufmt.Sprintf("negative epoch not supported: %d", epoch))
910 }
911
912 return utils.EncodeUint64(uint64(epoch))
913}
914
915func decodeEpochKey(key string) int64 {
916 return gnsmath.SafeUint64ToInt64(utils.DecodeUint64(key))
917}
918
919// NewProtocolFeeStore creates a protocol-fee store backed by the provided KV store.
920// This function is used by the upgrade system to create storage instances for each implementation.
921//
922// Parameters:
923// - kvStore: Domain KV store used to persist protocol-fee state.
924//
925// Returns:
926// - protocolFeeStore: An IProtocolFeeStore implementation backed by kvStore.
927func NewProtocolFeeStore(kvStore store.KVStore) IProtocolFeeStore {
928 return &protocolFeeStore{
929 kvStore: kvStore,
930 }
931}