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

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}