Kind Realm (renderable)
Name v1
Namespace gnoswap / staker
Exported functions 14
Module gno.land/r/gnoswap/staker/v1
gno 0.9 reward_calculation_pool_tier.gno gno
⧉
1 package staker 2 3 import ( 4 "errors" 5 6 "gno.land/p/gnoswap/gnsmath/v1" 7 bptree "gno.land/p/nt/bptree/v0" 8 NewDepositResolver (deposit *gno.land/r/gnoswap/staker.Deposit) *gno.land/r/gnoswap/staker/v1.DepositResolver
NewDeposits () *gno.land/r/gnoswap/staker/v1.Deposits
NewExternalIncentiveResolver (externalIncentive *gno.land/r/gnoswap/staker.ExternalIncentive) *gno.land/r/gnoswap/staker/v1.ExternalIncentiveResolver
NewExternalIncentives () *gno.land/r/gnoswap/staker/v1.ExternalIncentives
NewIncentivesResolver (incentives *gno.land/r/gnoswap/staker.Incentives) *gno.land/r/gnoswap/staker/v1.IncentivesResolver
NewPoolResolver (pool *gno.land/r/gnoswap/staker.Pool) *gno.land/r/gnoswap/staker/v1.PoolResolver
NewPools () *gno.land/r/gnoswap/staker/v1.Pools
NewPoolTier (pools *gno.land/r/gnoswap/staker/v1.Pools, currentTime int64, initialPoolPath string, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier
NewPoolTierBy (membership *gno.land/p/nt/bptree/v0.BPTree, tierRatio struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}, counts [4]uint64, lastRewardCacheTimestamp int64, currentEmission int64, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier
NewStakerV1 (stakerStore interface {AddAllowedToken func(int, .uverse.realm, string) .uverse.error; AddDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; GetAllowedTokens func() []string; GetCurrentSwapBatch func() *gno.land/r/gnoswap/staker.SwapBatchProcessor; GetDeniedRewardTokens func() []string; GetDepositGnsAmount func() int64; GetDeposits func() *gno.land/p/nt/bptree/v0.BPTree; GetExternalIncentives func() *gno.land/p/nt/bptree/v0.BPTree; GetIncentiveCounter func() *gno.land/r/gnoswap/staker.Counter; GetMinimumRewardAmount func() int64; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolTierCounts func() [4]uint64; GetPoolTierCurrentEmission func() int64; GetPoolTierGetEmission func() func() (int64, .uverse.error); GetPoolTierGetHalvingBlocksInRange func() func(int64, int64) ([]int64, []int64, .uverse.error); GetPoolTierLastRewardCacheTimestamp func() int64; GetPoolTierMemberships func() *gno.land/p/nt/bptree/v0.BPTree; GetPoolTierRatio func() gno.land/r/gnoswap/staker.TierRatio; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetTokenSpecificMinimumRewards func() map[string]int64; GetTotalEmissionSent func() int64; GetUncollectedIncentiveCounts func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakedPositions func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakingFee func() uint64; GetWarmupTemplate func() []gno.land/r/gnoswap/staker.Warmup; HasAllowedTokensStoreKey func() bool; HasCurrentSwapBatchStoreKey func() bool; HasDeniedRewardTokensStoreKey func() bool; HasDepositGnsAmountStoreKey func() bool; HasDepositsStoreKey func() bool; HasExternalIncentivesStoreKey func() bool; HasIncentiveCounterStoreKey func() bool; HasMinimumRewardAmountStoreKey func() bool; HasPendingProtocolFeesStoreKey func() bool; HasPoolTierCountsStoreKey func() bool; HasPoolTierCurrentEmissionStoreKey func() bool; HasPoolTierGetEmissionStoreKey func() bool; HasPoolTierGetHalvingBlocksInRangeStoreKey func() bool; HasPoolTierLastRewardCacheTimestampStoreKey func() bool; HasPoolTierMembershipsStoreKey func() bool; HasPoolTierRatioStoreKey func() bool; HasPoolsStoreKey func() bool; HasTokenSpecificMinimumRewardsStoreKey func() bool; HasTotalEmissionSentStoreKey func() bool; HasUncollectedIncentiveCountsStoreKey func() bool; HasUnstakedPositionsStoreKey func() bool; HasUnstakingFeeStoreKey func() bool; HasWarmupTemplateStoreKey func() bool; NextIncentiveID func(.uverse.address, int64) string; RemoveAllowedToken func(int, .uverse.realm, string) .uverse.error; RemoveDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; RemoveTokenSpecificMinimumRewardItem func(int, .uverse.realm, string) .uverse.error; SetAllowedTokens func(int, .uverse.realm, []string) .uverse.error; SetCurrentSwapBatch func(int, .uverse.realm, *gno.land/r/gnoswap/staker.SwapBatchProcessor) .uverse.error; SetDepositGnsAmount func(int, .uverse.realm, int64) .uverse.error; SetDeposits func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetExternalIncentives func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetIncentiveCounter func(int, .uverse.realm, *gno.land/r/gnoswap/staker.Counter) .uverse.error; SetMinimumRewardAmount func(int, .uverse.realm, int64) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolTierCounts func(int, .uverse.realm, [4]uint64) .uverse.error; SetPoolTierCurrentEmission func(int, .uverse.realm, int64) .uverse.error; SetPoolTierGetEmission func(int, .uverse.realm, func() (int64, .uverse.error)) .uverse.error; SetPoolTierGetHalvingBlocksInRange func(int, .uverse.realm, func(int64, int64) ([]int64, []int64, .uverse.error)) .uverse.error; SetPoolTierLastRewardCacheTimestamp func(int, .uverse.realm, int64) .uverse.error; SetPoolTierMemberships func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPoolTierRatio func(int, .uverse.realm, gno.land/r/gnoswap/staker.TierRatio) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetTokenSpecificMinimumRewardItem func(int, .uverse.realm, string, int64) .uverse.error; SetTokenSpecificMinimumRewards func(int, .uverse.realm, map[string]int64) .uverse.error; SetTotalEmissionSent func(int, .uverse.realm, int64) .uverse.error; SetUncollectedIncentiveCounts func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakedPositions func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakingFee func(int, .uverse.realm, uint64) .uverse.error; SetWarmupTemplate func(int, .uverse.realm, []gno.land/r/gnoswap/staker.Warmup) .uverse.error}, poolAccessor interface {ExistsPoolPath func(string) bool; GetSlot0SqrtPriceX96 func(string) string; GetSlot0Tick func(string) int32; SetSwapEndHook func(int, .uverse.realm, func(int, .uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(int, .uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(int, .uverse.realm, string, int32, bool, int64))}, emissionAccessor interface {GetStakerEmissionAmountPerSecond func() (int64, .uverse.error); GetStakerEmissionAmountPerSecondInRange func(int64, int64) ([]int64, []int64, .uverse.error); MintAndDistributeGns func(int, .uverse.realm) (int64, bool); SetOnDistributionPctChangeCallback func(int, .uverse.realm, func(int, .uverse.realm, int64))}, nftAccessor interface {Approve func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error; Burn func(int, .uverse.realm, gno.land/p/nt/grc721/v0.TokenID); Exists func(gno.land/p/nt/grc721/v0.TokenID) bool; Mint func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) gno.land/p/nt/grc721/v0.TokenID; MustOwnerOf func(gno.land/p/nt/grc721/v0.TokenID) .uverse.address; OwnerOf func(gno.land/p/nt/grc721/v0.TokenID) (.uverse.address, .uverse.error); TotalSupply func() int64; TransferFrom func(int, .uverse.realm, .uverse.address, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error})
NewTickCrossEventInfo (tickID int32, stakedLiquidityGross *gno.land/p/gnoswap/uint256/v1.Uint, stakedLiquidityDelta *gno.land/p/gnoswap/int256/v1.Int, outsideAccumulation *gno.land/p/gnoswap/uint256/v1.Uint) *gno.land/r/gnoswap/staker/v1.tickCrossEventInfo
NewTickResolver (tick *gno.land/r/gnoswap/staker.Tick) *gno.land/r/gnoswap/staker/v1.TickResolver
NewUnstakedPositions () *gno.land/r/gnoswap/staker/v1.UnstakedPositions
TierRatioFromCounts (tier1Count uint64, tier2Count uint64, tier3Count uint64) struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 sr "gno.land/r/gnoswap/staker"
10 )
11
12 const (
13 AllTierCount = 4 // 0, 1, 2, 3
14 Tier1 = 1
15 Tier2 = 2
16 Tier3 = 3
17 )
18
19 // TierRatioFromCounts calculates the ratio distribution for each tier based on pool counts.
20 //
21 // Parameters:
22 // - tier1Count (uint64): Number of pools in tier 1.
23 // - tier2Count (uint64): Number of pools in tier 2.
24 // - tier3Count (uint64): Number of pools in tier 3.
25 //
26 // Returns:
27 // - TierRatio: The ratio distribution across tier 1, 2, and 3, scaled up by 100.
28 func TierRatioFromCounts(tier1Count, tier2Count, tier3Count uint64 ) sr.TierRatio {
29 // tier1 always exists.
30 //
31 // TierRatio is declared in /r/gnoswap/staker; constructing it via a
32 // composite literal here (/r/gnoswap/staker/v1) trips the construction-time
33 // check ("cannot allocate ... in realm ..."). Route through the domain
34 // constructor sr.NewTierRatio so allocation happens in the declaring realm.
35 if tier2Count == 0 && tier3Count == 0 {
36 return sr.NewTierRatio( 100 , 0 , 0 )
37 }
38 if tier2Count == 0 {
39 return sr.NewTierRatio( 80 , 0 , 20 )
40 }
41 if tier3Count == 0 {
42 return sr.NewTierRatio( 70 , 30 , 0 )
43 }
44 return sr.NewTierRatio( 50 , 30 , 20 )
45 }
46
47 // PoolTier manages pool counts, ratios, and rewards for different tiers.
48 //
49 // Fields:
50 // - membership: Tracks which tier a pool belongs to (poolPath -> blockNumber -> tier).
51 //
52 // Methods:
53 // - CurrentCount: Returns the current count of pools in a tier at a specific timestamp.
54 // - CurrentRatio: Returns the current ratio for a tier at a specific timestamp.
55 // - CurrentTier: Returns the tier of a specific pool at a given timestamp.
56 // - CurrentReward: Retrieves the reward for a tier at a specific timestamp.
57 // - changeTier: Updates the tier of a pool and recalculates ratios.
58 type PoolTier struct {
59 membership *bptree.BPTree // poolPath -> tier(1, 2, 3)
60
61 tierRatio sr.TierRatio
62
63 counts [AllTierCount] uint64
64
65 lastRewardCacheTimestamp int64
66
67 currentEmission int64
68
69 // returns current emission.
70 getEmission func () ( int64 , error )
71 // Returns a list of halving timestamps and their emission amounts within the interval [start, end) in ascending order.
72 // The first return value is a list of timestamps where halving occurs.
73 // The second return value is a list of emission amounts corresponding to each halving timestamp.
74 getHalvingBlocksInRange func (start, end int64 ) ([] int64 , [] int64 , error )
75 }
76
77 // NewPoolTier creates a new PoolTier instance with single initial 1 tier pool.
78 //
79 // Parameters:
80 // - pools: The pool collection.
81 // - currentTime: The current block time.
82 // - initialPoolPath: The path of the initial pool.
83 // - getEmission: A function that returns the current emission to the staker contract.
84 // - getHalvingBlocksInRange: A function that returns a list of halving blocks within the interval [start, end) in ascending order.
85 //
86 // Returns:
87 // - *PoolTier: The new PoolTier instance.
88 func NewPoolTier(pools *Pools, currentTime int64 , initialPoolPath string , getEmission func () ( int64 , error ), getHalvingBlocksInRange func (start, end int64 ) ([] int64 , [] int64 , error )) *PoolTier {
89 currentEmission, err := getEmission()
90 if err != nil {
91 panic(err)
92 }
93
94 result := &PoolTier{
95 membership: sr.NewBPTreeN( 16 ),
96 tierRatio: TierRatioFromCounts( 1 , 0 , 0 ),
97 lastRewardCacheTimestamp: gnsmath.SafeAddInt64(currentTime, 1 ),
98 getEmission: getEmission,
99 getHalvingBlocksInRange: getHalvingBlocksInRange,
100 currentEmission: currentEmission,
101 }
102
103 pools.set(initialPoolPath, sr.NewPool(initialPoolPath, currentTime+ 1 ))
104 result.changeTier(currentTime+ 1 , pools, initialPoolPath, 1 )
105 return result
106 }
107
108 // NewPoolTierBy reconstructs a PoolTier from persisted membership, ratios, counts, and callbacks.
109 //
110 // Parameters:
111 // - membership: Persisted pool-path-to-tier mapping.
112 // - tierRatio: Persisted reward-share ratio for each tier.
113 // - counts: Persisted number of pools in each tier index.
114 // - lastRewardCacheTimestamp: Timestamp through which reward caches have been materialized.
115 // - currentEmission: Emission rate currently used for reward accrual.
116 // - getEmission: Callback returning the current staker emission rate or an error.
117 // - getHalvingBlocksInRange: Callback returning halving timestamps and corresponding emissions in [start, end).
118 //
119 // Returns:
120 // - *PoolTier: Reconstructed tier manager using the supplied persisted state and callbacks.
121 func NewPoolTierBy(
122 membership *bptree.BPTree,
123 tierRatio sr.TierRatio,
124 counts [AllTierCount] uint64 ,
125 lastRewardCacheTimestamp int64 ,
126 currentEmission int64 ,
127 getEmission func () ( int64 , error ),
128 getHalvingBlocksInRange func (start, end int64 ) ([] int64 , [] int64 , error ),
129 ) *PoolTier {
130 return &PoolTier{
131 membership: membership,
132 tierRatio: tierRatio,
133 counts: counts,
134 lastRewardCacheTimestamp: lastRewardCacheTimestamp,
135 getEmission: getEmission,
136 getHalvingBlocksInRange: getHalvingBlocksInRange,
137 currentEmission: currentEmission,
138 }
139 }
140
141 // CurrentReward returns the current per-pool reward for the given tier.
142 // Parameters:
143 // - tier: Tier number whose current emission reward is requested.
144 //
145 // Returns:
146 // - int64: Current per-pool reward for the tier, or zero when the calculation cannot produce a reward.
147 // - error: Error from emission lookup, invalid-tier lookup, or reward calculation; nil when the reward calculation succeeds.
148 func (self *PoolTier) CurrentReward(tier uint64 ) ( int64 , error ) {
149 currentEmission, err := self.getEmission()
150 if err != nil {
151 return 0 , err
152 }
153 tierRatio, err := self.tierRatio.Get(tier)
154 if err != nil {
155 return 0 , makeErrorWithDetails(errInvalidPoolTier, err.Error())
156 }
157
158 tierRatioInt64 := int64 (tierRatio)
159 count := int64 (self.CurrentCount(tier))
160
161 return calculatePoolReward(currentEmission, tierRatioInt64, count)
162 }
163
164 // CurrentCount returns the current count of pools in the given tier.
165 // Parameters:
166 // - tier: Tier index whose pool count is requested; out-of-range indexes return zero.
167 //
168 // Returns:
169 // - int: Number of pools currently assigned to tier.
170 func (self *PoolTier) CurrentCount(tier uint64 ) int {
171 if tier >= AllTierCount {
172 return 0
173 }
174 return int (self.counts[tier])
175 }
176
177 // CurrentAllTierCounts returns the current count of pools in each tier.
178 // Returns:
179 // - []uint64: Snapshot of pool counts for tier indexes 0 through AllTierCount-1.
180 func (self *PoolTier) CurrentAllTierCounts() [] uint64 {
181 out := make([] uint64 , AllTierCount)
182 copy(out, self.counts[:])
183 return out // returning snapshot
184 }
185
186 // CurrentTier returns the tier of the given pool.
187 // Parameters:
188 // - poolPath: Pool path whose current tier membership is requested.
189 //
190 // Returns:
191 // - tier: Current tier number, or zero when the pool is not in the membership tree.
192 func (self *PoolTier) CurrentTier(poolPath string ) (tier uint64 ) {
193 if tierI := self.membership.Get(poolPath); tierI == nil {
194 return 0
195 } else {
196 var ok bool
197 tier, ok = tierI.( uint64 )
198 if !ok {
199 panic( "failed to cast tier to uint64" )
200 }
201 return tier
202 }
203 }
204
205 // changeTier updates the tier of a pool, recalculates ratios, and applies
206 // updated per-pool reward to each of the pools.
207 func (self *PoolTier) changeTier(currentTime int64 , pools *Pools, poolPath string , nextTier uint64 ) map [ uint64 ] int64 {
208 currentTier := self.CurrentTier(poolPath)
209 if currentTier == nextTier {
210 // no change, return
211 return make( map [ uint64 ] int64 )
212 }
213 assertTier1HasSparePool(currentTier, self.counts[Tier1])
214
215 self.cacheReward(currentTime, pools)
216
217 // decrement count from current tier if it exists
218 if currentTier > 0 {
219 if self.counts[currentTier] == 0 {
220 panic( "counts underflow: removing from empty tier" )
221 }
222 self.counts[currentTier]--
223 }
224
225 if nextTier == 0 {
226 // removed from the tier
227 self.membership.Remove(poolPath)
228 pool, ok := pools.Get(poolPath)
229 if !ok {
230 panic( "changeTier: pool not found" )
231 }
232 poolResolver := NewPoolResolver(pool)
233 // prevent new rewards from accumulating after tier removal
234 poolResolver.cacheReward(currentTime, 0 )
235 } else {
236 // handle all move/add operations
237 self.membership.Set(poolPath, nextTier)
238 self.counts[nextTier]++
239 }
240
241 self.tierRatio = TierRatioFromCounts(self.counts[Tier1], self.counts[Tier2], self.counts[Tier3])
242 currentEmission, err := self.getEmission()
243 if err != nil {
244 panic(err)
245 }
246 tierRewards := self.computeTierRewards(currentEmission)
247
248 // Cache updated reward for each tiered pool
249 self.membership.Iterate( "" , "" , func (key string , value any ) bool {
250 pool, ok := pools.Get(key)
251 if !ok {
252 panic( "changeTier: pool not found" )
253 }
254 tier, ok := value.( uint64 )
255 if !ok {
256 panic( "failed to cast value to uint64" )
257 }
258
259 poolReward, ok := tierRewards[tier]
260 if !ok {
261 return false // Skip if no pools in tier
262 }
263
264 poolResolver := NewPoolResolver(pool)
265 poolResolver.cacheReward(currentTime, poolReward)
266 return false
267 })
268
269 self.currentEmission = currentEmission
270
271 return tierRewards
272 }
273
274 // cacheReward MUST be called before calculating any position reward.
275 // cacheReward updates the reward cache for each pool, accounting for any halving events
276 // that occurred between the last cached timestamp and the current timestamp.
277 // Note: Block height is used only for event tracking purposes.
278 // tierContextOf returns the pool's tier and the ratio/count setting its current emission rate.
279 func (self *PoolTier) tierContextOf(poolPath string ) ( uint64 , uint64 , uint64 , error ) {
280 tier := self.CurrentTier(poolPath)
281 if tier == 0 || tier >= AllTierCount {
282 return tier, 0 , 0 , nil
283 }
284
285 ratio, err := self.tierRatio.Get(tier)
286 if err != nil {
287 return 0 , 0 , 0 , makeErrorWithDetails(errInvalidPoolTier, err.Error())
288 }
289
290 return tier, ratio, self.counts[tier], nil
291 }
292
293 func (self *PoolTier) cacheReward(currentTimestamp int64 , pools *Pools) {
294 lastTimestamp := self.lastRewardCacheTimestamp
295
296 if currentTimestamp <= lastTimestamp {
297 // no need to check
298 return
299 }
300
301 // find halving blocks in range
302 halvingTimestamps, halvingEmissions, err := self.getHalvingBlocksInRange(lastTimestamp, currentTimestamp)
303 if err != nil {
304 panic(err)
305 }
306
307 if len(halvingTimestamps) == 0 {
308 self.applyCacheToAllPools(pools, currentTimestamp, self.currentEmission)
309 self.lastRewardCacheTimestamp = currentTimestamp
310 return
311 }
312
313 for i, hvTimestamp := range halvingTimestamps {
314 emission := halvingEmissions[i]
315 // caching: [lastTimestamp, hvTimestamp)
316 self.applyCacheToAllPools(pools, hvTimestamp, emission)
317
318 // halve emissions when halvingBlock is reached
319 self.currentEmission = emission
320 }
321
322 // remaining range [lastTimestamp, currentTimestamp)
323 self.applyCacheToAllPools(pools, currentTimestamp, self.currentEmission)
324
325 self.lastRewardCacheTimestamp = currentTimestamp
326 }
327
328 // cacheRewardForPool caches internal reward/accumulators for a single pool only.
329 // This avoids iterating all tiered pools on every position reward calculation.
330 func (self *PoolTier) cacheRewardForPool(currentTimestamp int64 , pools *Pools, poolPath string ) {
331 pool, ok := pools.Get(poolPath)
332 if !ok {
333 return
334 }
335
336 tierNum := self.CurrentTier(poolPath)
337 // Pool not in the internal-incentive system.
338 if tierNum == 0 {
339 return
340 }
341
342 // Find the latest reward cache timestamp for this pool.
343 lastTimestamp := int64 ( 0 )
344 hasLast := false
345 pool.RewardCache().ReverseIterate( 0 , currentTimestamp, func (key int64 , _ any ) bool {
346 lastTimestamp = key
347 hasLast = true
348 return true
349 })
350
351 if !hasLast {
352 // Fallback to global tier cache cursor.
353 lastTimestamp = self.lastRewardCacheTimestamp
354 }
355
356 if currentTimestamp <= lastTimestamp {
357 return
358 }
359
360 // Determine halving boundaries since the pool's last cached reward timestamp.
361 halvingTimestamps, halvingEmissions, err := self.getHalvingBlocksInRange(lastTimestamp, currentTimestamp)
362 if err != nil {
363 panic(err)
364 }
365 poolResolver := NewPoolResolver(pool)
366
367 if len(halvingTimestamps) == 0 {
368 // No emission change within the range => use the live emission.
369 currentEmission, err := self.getEmission()
370 if err != nil {
371 panic(err)
372 }
373 self.applyCacheToPool(poolResolver, tierNum, currentTimestamp, currentEmission)
374 return
375 }
376
377 // Apply caching at every halving boundary.
378 currentEmission := int64 ( 0 )
379 for i, hvTimestamp := range halvingTimestamps {
380 currentEmission = halvingEmissions[i]
381 self.applyCacheToPool(poolResolver, tierNum, hvTimestamp, currentEmission)
382 }
383
384 // Remaining range [lastTimestamp, currentTimestamp).
385 self.applyCacheToPool(poolResolver, tierNum, currentTimestamp, currentEmission)
386 }
387
388 // applyCacheToPool applies the cached reward to all tiered pool.
389 func (self *PoolTier) applyCacheToPool(poolResolver *PoolResolver, tierNum uint64 , currentTimestamp, emissionInThisInterval int64 ) {
390 tierRewards := self.computeTierRewards(emissionInThisInterval)
391 poolReward, ok := tierRewards[tierNum]
392 if !ok {
393 return
394 }
395
396 poolResolver.cacheReward(currentTimestamp, poolReward)
397 }
398
399 // applyCacheToAllPools applies the cached reward to all tiered pools.
400 func (self *PoolTier) applyCacheToAllPools(pools *Pools, currentTimestamp, emissionInThisInterval int64 ) {
401 // calculate denominator and number of pools in each tier
402 counts := self.CurrentAllTierCounts()
403 tierRewards := self.computeTierRewards(emissionInThisInterval)
404
405 // apply cache to all pools
406 self.membership.Iterate( "" , "" , func (key string , value any ) bool {
407 pool, ok := pools.Get(key)
408 if !ok {
409 return false
410 }
411
412 tierNum, ok := value.( uint64 )
413 if !ok {
414 panic( "failed to cast value to uint64" )
415 }
416 // Skip pools with tier 0 (removed from tier system)
417 if tierNum == 0 {
418 return false
419 }
420
421 if counts[tierNum] == 0 {
422 return false // Skip if no pools in tier
423 }
424
425 poolReward, ok := tierRewards[tierNum]
426 if !ok {
427 return false
428 }
429
430 // accumulate the reward for the interval (startBlock to endBlock) in the Pool
431 poolResolver := NewPoolResolver(pool)
432 poolResolver.cacheReward(currentTimestamp, poolReward)
433 return false
434 })
435 }
436
437 // IsInternallyIncentivizedPool returns true if the pool is in a tier.
438 // Parameters:
439 // - poolPath: Pool path whose membership is checked.
440 //
441 // Returns:
442 // - bool: True when poolPath belongs to a nonzero internal-incentive tier; false otherwise.
443 func (self *PoolTier) IsInternallyIncentivizedPool(poolPath string ) bool {
444 return self.CurrentTier(poolPath) > 0
445 }
446
447 // calculatePoolReward calculates the reward for a pool based on the emission, tier ratio, and tier count.
448 //
449 // Parameters:
450 // - emission: The emission for the pool.
451 // - tierRatio: The tier ratio for the pool.
452 // - tierCount: The tier count for the pool.
453 //
454 // Returns:
455 // - int64: The reward for the pool.
456 // - error: An invalid calculation input.
457 func calculatePoolReward(emission int64 , tierRatio int64 , tierCount int64 ) ( int64 , error ) {
458 if emission < 0 || tierRatio < 0 || tierCount < 0 {
459 return 0 , errors.New(errCalculationError)
460 }
461
462 if emission == 0 || tierRatio == 0 || tierCount == 0 {
463 return 0 , nil
464 }
465
466 tierReward := gnsmath.SafeMulDivInt64(emission, tierRatio, 100 )
467
468 return tierReward / tierCount, nil
469 }
470
471 // computeTierRewards caches per-tier pool rewards to avoid recalculating for each pool iteration.
472 func (self *PoolTier) computeTierRewards(emission int64 ) map [ uint64 ] int64 {
473 tierRewards := make( map [ uint64 ] int64 , AllTierCount- 1 )
474
475 for tierNum := uint64 ( 1 ); tierNum < AllTierCount; tierNum++ {
476 tierCount := int64 (self.counts[tierNum])
477 if tierCount == 0 {
478 continue
479 }
480
481 tierRatio, err := self.tierRatio.Get(tierNum)
482 if err != nil {
483 panic(makeErrorWithDetails(errInvalidPoolTier, err.Error()))
484 }
485
486 reward, err := calculatePoolReward(emission, int64 (tierRatio), tierCount)
487 if err != nil {
488 panic(err)
489 }
490 tierRewards[tierNum] = reward
491 }
492
493 return tierRewards
494 }
495
*gno.land/r/gnoswap/staker/v1.stakerV1