Search Apps Documentation Source Content File Folder Download Copy Actions Download

reward_calculation_pool.gno

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