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

store.gno

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}