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

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}