reward_calculation_pool.gno
20.83 Kb · 640 lines
1package staker
2
3import (
4 "errors"
5 "time"
6
7 "gno.land/p/gnoswap/gnsmath"
8 bptree "gno.land/p/nt/bptree/v0"
9 ufmt "gno.land/p/nt/ufmt/v0"
10
11 i256 "gno.land/p/gnoswap/int256"
12 u256 "gno.land/p/gnoswap/uint256"
13 sr "gno.land/r/gnoswap/staker"
14)
15
16var q128 = u256.MustFromDecimal("340282366920938463463374607431768211456")
17
18// Pools represents the global pool storage
19type Pools struct {
20 tree *bptree.BPTree // string poolPath -> pool
21}
22
23func NewPools() *Pools {
24 return &Pools{
25 tree: sr.NewBPTreeN(16),
26 }
27}
28
29// Get returns the pool for the given poolPath
30func (self *Pools) Get(poolPath string) (*sr.Pool, bool) {
31 v := self.tree.Get(poolPath)
32 if v == nil {
33 return nil, false
34 }
35 p, ok := v.(*sr.Pool)
36 if !ok {
37 panic(ufmt.Sprintf("failed to cast v to *Pool: %T", v))
38 }
39 return p, true
40}
41
42// GetPoolOrNil returns the pool for the given poolPath, or returns nil if it does not exist
43func (self *Pools) GetPoolOrNil(poolPath string) *sr.Pool {
44 pool, ok := self.Get(poolPath)
45 if !ok {
46 return nil
47 }
48 return pool
49}
50
51// set sets the pool for the given poolPath.
52func (self *Pools) set(poolPath string, pool *sr.Pool) {
53 self.tree.Set(poolPath, pool)
54}
55
56// Has returns true if the pool exists for the given poolPath
57func (self *Pools) Has(poolPath string) bool {
58 return self.tree.Has(poolPath)
59}
60
61func (self *Pools) IterateAll(fn func(key string, pool *sr.Pool) bool) {
62 self.tree.Iterate("", "", func(key string, value any) bool {
63 p, ok := value.(*sr.Pool)
64 if !ok {
65 panic(ufmt.Sprintf("failed to cast value to *Pool: %T", value))
66 }
67 return fn(key, p)
68 })
69}
70
71type PoolResolver struct {
72 *sr.Pool
73}
74
75func (self *PoolResolver) IncentivesResolver() *IncentivesResolver {
76 return NewIncentivesResolver(self.Incentives())
77}
78
79// Get the latest global reward ratio accumulation in [0, currentTime] range.
80// Returns the time and the accumulation.
81func (self *PoolResolver) CurrentGlobalRewardRatioAccumulation(currentTime int64) (time int64, acc string) {
82 acc = "0"
83
84 self.GlobalRewardRatioAccumulation().ReverseIterate(0, currentTime, func(key int64, value any) bool {
85 time = key
86
87 valueStr, ok := value.(string)
88 if !ok {
89 panic(ufmt.Sprintf("failed to cast value to string: %T", value))
90 }
91
92 acc = valueStr
93
94 return true
95 })
96
97 return time, acc
98}
99
100// Get the latest tick in [0, currentTime] range.
101// Returns the tick.
102func (self *PoolResolver) CurrentTick(currentTime int64) (tick int32) {
103 self.HistoricalTick().ReverseIterate(0, currentTime, func(key int64, value any) bool {
104 res, ok := value.(int32)
105 if !ok {
106 panic(ufmt.Sprintf("failed to cast value to int32: %T", value))
107 }
108 tick = res
109 return true
110 })
111 return tick
112}
113
114func (self *PoolResolver) CurrentStakedLiquidity(currentTime int64) (liquidity *u256.Uint) {
115 liquidity = u256.Zero()
116
117 self.StakedLiquidity().ReverseIterate(0, currentTime, func(key int64, value any) bool {
118 res, ok := value.(*u256.Uint)
119 if !ok {
120 panic(ufmt.Sprintf("failed to cast value to *u256.Uint: %T", value))
121 }
122 liquidity = res
123 return true
124 })
125 return liquidity
126}
127
128func (self *PoolResolver) TickResolver(tickId int32) *TickResolver {
129 return NewTickResolver(self.Ticks().Get(tickId))
130}
131
132// IsExternallyIncentivizedPool returns true if the pool has any active external incentives.
133func (self *PoolResolver) IsExternallyIncentivizedPool() bool {
134 currentTime := time.Now().Unix()
135 hasIncentive := false
136 self.Incentives().IncentiveTrees().Iterate("", "", func(key string, value any) bool {
137 incentive, ok := value.(*sr.ExternalIncentive)
138 if !ok {
139 panic("failed to cast value to *ExternalIncentive")
140 }
141
142 resolver := NewExternalIncentiveResolver(incentive)
143 if !resolver.IsEnded(currentTime) {
144 hasIncentive = true
145 return true
146 }
147
148 return false
149 })
150
151 return hasIncentive
152}
153
154// Get the latest reward in [0, currentTime] range.
155// Returns the reward.
156func (self *PoolResolver) CurrentReward(currentTime int64) (reward int64) {
157 self.RewardCache().ReverseIterate(0, currentTime, func(key int64, value any) bool {
158 res, ok := value.(int64)
159 if !ok {
160 panic(ufmt.Sprintf("failed to cast value to int64: %T", value))
161 }
162 reward = res
163 return true
164 })
165 return reward
166}
167
168func (self *PoolResolver) isChangedTick(currentTime int64, currentTick int32) bool {
169 if self.HistoricalTick().Size() == 0 {
170 return true
171 }
172
173 previousTick := self.CurrentTick(currentTime)
174
175 return previousTick != currentTick
176}
177
178// cacheReward sets the current reward for the pool
179// If the pool is in unclaimable period, it will end the unclaimable period, updates the reward, and start the unclaimable period again.
180//
181// Important behavior for initial tier assignment:
182// - When a pool first receives a tier, oldTierReward=0 and currentTierReward>0
183// - If the pool has zero liquidity at this point, startUnclaimablePeriod() is called
184// - This ensures unclaimable period tracking begins from the moment rewards start emitting
185func (self *PoolResolver) cacheReward(currentTime int64, currentTierReward int64) {
186 oldTierReward := self.CurrentReward(currentTime)
187 if oldTierReward == currentTierReward {
188 return
189 }
190
191 isInUnclaimable := self.CurrentStakedLiquidity(currentTime).IsZero()
192 if isInUnclaimable {
193 // End any existing unclaimable period
194 // Note: If lastUnclaimableTime is 0 (not yet tracking), this is a no-op
195 self.endUnclaimablePeriod(currentTime)
196 }
197
198 self.Pool.SetRewardCacheAt(currentTime, currentTierReward)
199
200 if isInUnclaimable {
201 // Start/restart unclaimable period tracking
202 // This handles initial tier assignment when lastUnclaimableTime is 0
203 self.startUnclaimablePeriod(currentTime)
204 }
205}
206
207// cacheInternalReward caches the current emission and updates the global reward ratio accumulation.
208func (self *PoolResolver) cacheInternalReward(currentTime int64, currentEmission int64) {
209 self.cacheReward(currentTime, currentEmission)
210
211 currentStakedLiquidity := self.CurrentStakedLiquidity(currentTime)
212 self.updateGlobalRewardRatioAccumulation(currentTime, currentStakedLiquidity)
213}
214
215func (self *PoolResolver) calculateGlobalRewardRatioAccumulation(currentTime int64, currentStakedLiquidity *u256.Uint) *u256.Uint {
216 oldAccTime, oldAccStr := self.CurrentGlobalRewardRatioAccumulation(currentTime)
217 timeDiff := gnsmath.SafeSubInt64(currentTime, oldAccTime)
218 if timeDiff == 0 {
219 return u256.MustFromDecimal(oldAccStr)
220 }
221 if timeDiff < 0 {
222 panic("time cannot go backwards")
223 }
224
225 if currentStakedLiquidity.IsZero() {
226 return u256.MustFromDecimal(oldAccStr)
227 }
228
229 oldAcc := u256.MustFromDecimal(oldAccStr)
230 acc := u256.MulDiv(
231 u256.NewUintFromInt64(timeDiff),
232 q128,
233 currentStakedLiquidity,
234 )
235 return u256.Zero().Add(oldAcc, acc)
236}
237
238// updateGlobalRewardRatioAccumulation updates the global reward ratio accumulation and returns the new accumulation.
239func (self *PoolResolver) updateGlobalRewardRatioAccumulation(currentTime int64, currentStakedLiquidity *u256.Uint) *u256.Uint {
240 newAcc := self.calculateGlobalRewardRatioAccumulation(currentTime, currentStakedLiquidity)
241
242 // Persist as string to reduce stored object complexity.
243 self.Pool.SetGlobalRewardRatioAccumulationAt(currentTime, newAcc.ToString())
244 return newAcc
245}
246
247// RewardStateOf initializes a new RewardState for the given deposit.
248func (self *PoolResolver) RewardStateOf(deposit *sr.Deposit) *RewardState {
249 warmups := len(deposit.Warmups())
250 result := &RewardState{
251 pool: self,
252 deposit: NewDepositResolver(deposit),
253 rewards: make([]int64, warmups),
254 penalties: make([]int64, warmups),
255 }
256
257 return result
258}
259
260// reset clears cached rewards/penalties so a RewardState can be reused without re-allocating.
261func (self *RewardState) reset() {
262 for i := range self.rewards {
263 self.rewards[i] = 0
264 self.penalties[i] = 0
265 }
266}
267
268// NewPool creates a new pool with the given poolPath and currentHeight.
269func NewPoolResolver(pool *sr.Pool) *PoolResolver {
270 return &PoolResolver{
271 Pool: pool,
272 }
273}
274
275// RewardState is a struct for storing the intermediate state for reward calculation.
276type RewardState struct {
277 pool *PoolResolver
278 deposit *DepositResolver
279
280 // accumulated rewards for each warmup
281 rewards []int64
282 penalties []int64
283}
284
285// calculateInternalReward calculates the internal reward for the deposit.
286// It calls rewardPerWarmup for each rewardCache interval, applies warmup, and returns the rewards and penalties.
287func (self *RewardState) calculateInternalReward(startTime, endTime int64) ([]int64, []int64) {
288 currentReward := self.pool.CurrentReward(startTime)
289
290 self.pool.RewardCache().Iterate(startTime, endTime, func(key int64, value any) bool {
291 reward, ok := value.(int64)
292 if !ok {
293 panic(ufmt.Sprintf("failed to cast value to int64: %T", value))
294 }
295
296 // Calculate reward for the period before this cache entry
297 err := self.rewardPerWarmup(startTime, key, currentReward)
298 if err != nil {
299 panic(err)
300 }
301
302 startTime = key
303 currentReward = reward
304 return false
305 })
306
307 if startTime < endTime {
308 // For the remaining period, use the last cached reward value
309 // If the pool was de-tiered and the last cached value is 0, this ensures no new rewards
310 err := self.rewardPerWarmup(startTime, endTime, currentReward)
311 if err != nil {
312 panic(err)
313 }
314 }
315
316 self.applyWarmup()
317
318 return self.rewards, self.penalties
319}
320
321// updateExternalReward updates the external reward for the deposit.
322// It updates the last collect time for the external reward for the given incentive ID.
323// It returns an error if the current time is less than the last collect time for the external reward for the given incentive ID.
324func (self *RewardState) updateExternalReward(startTime, endTime int64, incentive *sr.ExternalIncentive) error {
325 lastCollectTime := self.deposit.ExternalRewardLastCollectTime(incentive.IncentiveId())
326 if startTime < lastCollectTime {
327 // This must not happen, but adding some guards just in case.
328 startTime = lastCollectTime
329 }
330
331 ictvStart := incentive.StartTimestamp()
332 if endTime < ictvStart {
333 return nil // Not started yet
334 }
335
336 if startTime < ictvStart {
337 startTime = ictvStart
338 }
339
340 ictvEnd := incentive.EndTimestamp()
341 if endTime > ictvEnd {
342 endTime = ictvEnd
343 }
344
345 if startTime > ictvEnd {
346 return nil // Already ended
347 }
348
349 return self.rewardPerWarmupX128(startTime, endTime, incentive.RewardPerSecondX128())
350}
351
352// calculateCollectableExternalReward calculates the calculated external reward for the deposit.
353// It calls updateExternalReward for the incentive period, applies warmup and returns the rewards and penalties.
354// used for reward calculation for a calculatable incentive
355func (self *RewardState) calculateCollectableExternalReward(startTime, endTime int64, incentive *sr.ExternalIncentive) int64 {
356 err := self.updateExternalReward(startTime, endTime, incentive)
357 if err != nil {
358 panic(err)
359 }
360
361 currentReward := u256.Zero()
362
363 for i := range self.rewards {
364 currentReward = currentReward.Add(currentReward, u256.NewUintFromInt64(self.rewards[i]))
365 }
366
367 return gnsmath.SafeConvertToInt64(currentReward)
368}
369
370// calculateExternalReward calculates the external reward for the deposit.
371// It calls rewardPerWarmup for startTime to endTime(clamped to the incentive period), applies warmup and returns the rewards and penalties.
372func (self *RewardState) calculateExternalReward(startTime, endTime int64, incentive *sr.ExternalIncentive) ([]int64, []int64) {
373 err := self.updateExternalReward(startTime, endTime, incentive)
374 if err != nil {
375 panic(err)
376 }
377
378 // apply warmup to collect rewards
379 self.applyWarmup()
380
381 return self.rewards, self.penalties
382}
383
384// applyWarmup applies the warmup to the rewards and calculate penalties.
385func (self *RewardState) applyWarmup() {
386 for i, warmup := range self.deposit.Warmups() {
387 warmupReward := self.rewards[i]
388
389 // calculate warmup reward applying warmup ratio
390 self.rewards[i] = gnsmath.SafeMulInt64(warmupReward, int64(warmup.WarmupRatio)) / 100
391
392 // warmup penalty is the difference between the warmup reward and the warmup reward applying warmup ratio
393 self.penalties[i] = gnsmath.SafeSubInt64(warmupReward, self.rewards[i])
394 }
395}
396
397// rewardPerWarmup calculates the reward for each warmup, adds to the RewardState's rewards array.
398// Used by the internal reward path where rewardPerSecond is an int64 emission rate.
399func (self *RewardState) rewardPerWarmup(startTime, endTime int64, rewardPerSecond int64) error {
400 // Return early if startTime equals endTime to avoid unnecessary computation
401 if startTime == endTime {
402 return nil
403 }
404
405 startTick := self.pool.CurrentTick(startTime)
406 startRaw := self.pool.CalculateRawRewardForPosition(startTime, startTick, self.deposit.Deposit)
407
408 for i, warmup := range self.deposit.Warmups() {
409 if startTime >= warmup.NextWarmupTime {
410 // passed the warmup
411 continue
412 }
413
414 if endTime < warmup.NextWarmupTime {
415 endTick := self.pool.CurrentTick(endTime)
416 endRaw := self.pool.CalculateRawRewardForPosition(endTime, endTick, self.deposit.Deposit)
417 rewardAcc, overflow := u256.Zero().SubOverflow(endRaw, startRaw)
418 if overflow {
419 panic(errors.New(errOverflow))
420 }
421
422 rewardAcc, overflow = u256.Zero().MulOverflow(rewardAcc, self.deposit.Liquidity())
423 if overflow {
424 panic(errors.New(errOverflow))
425 }
426
427 rewardAcc = u256.MulDiv(rewardAcc, u256.NewUintFromInt64(rewardPerSecond), q128)
428 self.rewards[i] = gnsmath.SafeAddInt64(self.rewards[i], gnsmath.SafeConvertToInt64(rewardAcc))
429
430 break
431 }
432
433 endTick := self.pool.CurrentTick(warmup.NextWarmupTime)
434 endRaw := self.pool.CalculateRawRewardForPosition(warmup.NextWarmupTime, endTick, self.deposit.Deposit)
435 rewardAcc, overflow := u256.Zero().SubOverflow(endRaw, startRaw)
436 if overflow {
437 panic(errors.New(errOverflow))
438 }
439
440 rewardAcc, overflow = u256.Zero().MulOverflow(rewardAcc, self.deposit.Liquidity())
441 if overflow {
442 panic(errors.New(errOverflow))
443 }
444
445 rewardAcc = u256.MulDiv(rewardAcc, u256.NewUintFromInt64(rewardPerSecond), q128)
446 self.rewards[i] = gnsmath.SafeAddInt64(self.rewards[i], gnsmath.SafeConvertToInt64(rewardAcc))
447
448 startTime = warmup.NextWarmupTime
449 startTick = endTick
450 startRaw = endRaw
451 }
452
453 return nil
454}
455
456// rewardPerWarmupX128 calculates the reward for each warmup using a Q128-scaled
457// per-second rate. Used by the external incentive path; the per-second rate is
458// stored as `(rewardAmount << 128) / duration` in ExternalIncentive, so an
459// extra `>> 128` is needed after the standard `MulDiv(rewardAcc, rps, q128)`
460// to materialize the integer result.
461func (self *RewardState) rewardPerWarmupX128(startTime, endTime int64, rewardPerSecondX128 *u256.Uint) error {
462 if startTime == endTime {
463 return nil
464 }
465
466 startTick := self.pool.CurrentTick(startTime)
467 startRaw := self.pool.CalculateRawRewardForPosition(startTime, startTick, self.deposit.Deposit)
468
469 for i, warmup := range self.deposit.Warmups() {
470 if startTime >= warmup.NextWarmupTime {
471 continue
472 }
473
474 if endTime < warmup.NextWarmupTime {
475 endTick := self.pool.CurrentTick(endTime)
476 endRaw := self.pool.CalculateRawRewardForPosition(endTime, endTick, self.deposit.Deposit)
477 rewardAcc, overflow := u256.Zero().SubOverflow(endRaw, startRaw)
478 if overflow {
479 panic(errors.New(errOverflow))
480 }
481
482 rewardAcc, overflow = u256.Zero().MulOverflow(rewardAcc, self.deposit.Liquidity())
483 if overflow {
484 panic(errors.New(errOverflow))
485 }
486
487 rewardAcc = u256.MulDiv(rewardAcc, rewardPerSecondX128, q128)
488 rewardAcc = u256.Zero().Rsh(rewardAcc, 128)
489 self.rewards[i] = gnsmath.SafeAddInt64(self.rewards[i], gnsmath.SafeConvertToInt64(rewardAcc))
490
491 break
492 }
493
494 endTick := self.pool.CurrentTick(warmup.NextWarmupTime)
495 endRaw := self.pool.CalculateRawRewardForPosition(warmup.NextWarmupTime, endTick, self.deposit.Deposit)
496 rewardAcc, overflow := u256.Zero().SubOverflow(endRaw, startRaw)
497 if overflow {
498 panic(errors.New(errOverflow))
499 }
500
501 rewardAcc, overflow = u256.Zero().MulOverflow(rewardAcc, self.deposit.Liquidity())
502 if overflow {
503 panic(errors.New(errOverflow))
504 }
505
506 rewardAcc = u256.MulDiv(rewardAcc, rewardPerSecondX128, q128)
507 rewardAcc = u256.Zero().Rsh(rewardAcc, 128)
508 self.rewards[i] = gnsmath.SafeAddInt64(self.rewards[i], gnsmath.SafeConvertToInt64(rewardAcc))
509
510 startTime = warmup.NextWarmupTime
511 startTick = endTick
512 startRaw = endRaw
513 }
514
515 return nil
516}
517
518// modifyDeposit updates the pool's staked liquidity and returns the new staked liquidity.
519// updates when there is a change in the staked liquidity(tick cross, stake, unstake)
520func (self *PoolResolver) modifyDeposit(delta *i256.Int, currentTime int64, nextTick int32) *u256.Uint {
521 // update staker side pool info
522 lastStakedLiquidity := self.CurrentStakedLiquidity(currentTime)
523 deltaApplied := gnsmath.LiquidityMathAddDelta(lastStakedLiquidity, delta)
524 result := self.updateGlobalRewardRatioAccumulation(currentTime, lastStakedLiquidity)
525
526 // historical tick does NOT actually reflect the tick at the timestamp, but it provides correct ordering for the staked positions
527 // because TickCrossHook is assured to be called for the staked-initialized ticks
528 if self.isChangedTick(currentTime, nextTick) {
529 self.Pool.SetHistoricalTickAt(currentTime, nextTick)
530 }
531
532 switch deltaApplied.Sign() {
533 case -1:
534 panic("stakedLiquidity is less than 0, should not happen")
535 case 0:
536 if lastStakedLiquidity.Sign() == 1 {
537 // StakedLiquidity moved from positive to zero, start unclaimable period
538 self.startUnclaimablePeriod(currentTime)
539 self.IncentivesResolver().startUnclaimablePeriod(currentTime)
540 }
541 case 1:
542 if lastStakedLiquidity.Sign() == 0 {
543 // StakedLiquidity moved from zero to positive, end unclaimable period
544 self.endUnclaimablePeriod(currentTime)
545 self.IncentivesResolver().endUnclaimablePeriod(currentTime)
546 }
547 }
548
549 self.Pool.SetStakedLiquidityAt(currentTime, deltaApplied)
550
551 return result
552}
553
554// startUnclaimablePeriod starts the unclaimable period.
555func (self *PoolResolver) startUnclaimablePeriod(currentTime int64) {
556 if self.LastUnclaimableTime() == 0 {
557 // We set only if it's the first time entering(0 indicates not set yet)
558 self.SetLastUnclaimableTime(currentTime)
559 }
560}
561
562// endUnclaimablePeriod ends the unclaimable period.
563// Accumulates to unclaimableAcc and resets lastUnclaimableTime to 0.
564func (self *PoolResolver) endUnclaimablePeriod(currentTime int64) {
565 if self.LastUnclaimableTime() == 0 {
566 // lastUnclaimableTime = 0 means tracking hasn't started yet
567 // This is normal during initial pool creation or when called from cacheReward
568 // during tier assignment with zero liquidity
569 return
570 }
571
572 self.updateUnclaimableAccumulateRewards(currentTime)
573 self.SetLastUnclaimableTime(0)
574}
575
576// updateUnclaimableAccumulateRewards ends the unclaimable period.
577// Accumulates to unclaimableAcc and resets lastUnclaimableTime to 0.
578func (self *PoolResolver) updateUnclaimableAccumulateRewards(currentTime int64) {
579 if self.LastUnclaimableTime() >= currentTime {
580 return
581 }
582
583 unclaimableDuration := gnsmath.SafeSubInt64(currentTime, self.LastUnclaimableTime())
584 currentUnclaimableReward := gnsmath.SafeMulInt64(unclaimableDuration, self.CurrentReward(self.LastUnclaimableTime()))
585 self.SetUnclaimableAcc(gnsmath.SafeAddInt64(self.UnclaimableAcc(), currentUnclaimableReward))
586}
587
588// processUnclaimableReward processes the unclaimable reward and returns the accumulated reward.
589// It resets unclaimableAcc to 0 and properly manages lastUnclaimableTime based on pool state.
590func (self *PoolResolver) processUnclaimableReward(endTime int64) int64 {
591 // Check current pool liquidity state
592 isZeroStakedLiquidity := self.CurrentStakedLiquidity(endTime).IsZero()
593
594 if self.LastUnclaimableTime() > 0 {
595 // We have an ongoing unclaimable period tracking
596 self.updateUnclaimableAccumulateRewards(endTime)
597
598 if isZeroStakedLiquidity {
599 // Still unclaimable - accumulate rewards up to endTime
600 // Update tracking time for continuing unclaimable period
601 self.SetLastUnclaimableTime(endTime)
602 } else {
603 // Was unclaimable but now has liquidity - properly end the period
604 self.SetLastUnclaimableTime(0)
605 }
606 } else {
607 if isZeroStakedLiquidity {
608 // No previous tracking but currently unclaimable - this shouldn't normally happen
609 // as startUnclaimablePeriod should have been called when liquidity reached 0
610 // Start tracking from now
611 self.SetLastUnclaimableTime(endTime)
612 }
613 }
614
615 // Return and reset accumulated unclaimable rewards
616 internalUnClaimable := self.UnclaimableAcc()
617 self.SetUnclaimableAcc(0)
618 return internalUnClaimable
619}
620
621// Calculates reward for a position *without* considering debt or warmup
622// It calculates the theoretical total reward for the position if it has been staked since the pool creation
623func (self *PoolResolver) CalculateRawRewardForPosition(currentTime int64, currentTick int32, deposit *sr.Deposit) *u256.Uint {
624 var rewardAcc *u256.Uint
625
626 globalAcc := self.calculateGlobalRewardRatioAccumulation(currentTime, self.CurrentStakedLiquidity(currentTime))
627
628 lowerAcc := self.TickResolver(deposit.TickLower()).CurrentOutsideAccumulation(currentTime)
629 upperAcc := self.TickResolver(deposit.TickUpper()).CurrentOutsideAccumulation(currentTime)
630 if currentTick < deposit.TickLower() {
631 rewardAcc = u256.Zero().Sub(lowerAcc, upperAcc)
632 } else if currentTick >= deposit.TickUpper() {
633 rewardAcc = u256.Zero().Sub(upperAcc, lowerAcc)
634 } else {
635 rewardAcc = u256.Zero().Sub(globalAcc, lowerAcc)
636 rewardAcc = rewardAcc.Sub(rewardAcc, upperAcc)
637 }
638
639 return rewardAcc
640}