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_incentives.gno

5.21 Kb · 184 lines
  1package staker
  2
  3import (
  4	"gno.land/p/gnoswap/gnsmath"
  5	u256 "gno.land/p/gnoswap/uint256"
  6	bptree "gno.land/p/nt/bptree/v0"
  7
  8	sr "gno.land/r/gnoswap/staker"
  9)
 10
 11type IncentivesResolver struct {
 12	*sr.Incentives
 13}
 14
 15func NewIncentivesResolver(incentives *sr.Incentives) *IncentivesResolver {
 16	return &IncentivesResolver{
 17		Incentives: incentives,
 18	}
 19}
 20
 21// Get incentive by incentiveId
 22func (self *IncentivesResolver) Get(incentiveId string) (*sr.ExternalIncentive, bool) {
 23	return retrieveIncentive(self.IncentiveTrees(), incentiveId)
 24}
 25
 26func (self *IncentivesResolver) GetIncentiveResolver(incentiveId string) (*ExternalIncentiveResolver, bool) {
 27	if incentive, ok := self.Get(incentiveId); ok {
 28		return NewExternalIncentiveResolver(incentive), true
 29	}
 30	return nil, false
 31}
 32
 33func retrieveIncentive(tree *bptree.BPTree, id string) (*sr.ExternalIncentive, bool) {
 34	value := tree.Get(id)
 35	if value == nil {
 36		return nil, false
 37	}
 38	v, ok := value.(*sr.ExternalIncentive)
 39	if !ok {
 40		panic("failed to cast value to *sr.ExternalIncentive")
 41	}
 42	return v, true
 43}
 44
 45// Create a new external incentive
 46// Panics if the incentive already exists.
 47func (self *IncentivesResolver) create(incentive *sr.ExternalIncentive) {
 48	self.Incentives.SetIncentive(incentive.IncentiveId(), incentive)
 49}
 50
 51// update updates an existing incentive with new information
 52func (self *IncentivesResolver) update(incentive *sr.ExternalIncentive) {
 53	self.Incentives.SetIncentive(incentive.IncentiveId(), incentive)
 54}
 55
 56// starts incentive unclaimable period for this pool
 57func (self *IncentivesResolver) startUnclaimablePeriod(startTimestamp int64) {
 58	self.Incentives.SetUnclaimablePeriod(startTimestamp, int64(0))
 59}
 60
 61// ends incentive unclaimable period for this pool
 62// ignores if currently not in unclaimable period
 63func (self *IncentivesResolver) endUnclaimablePeriod(endTimestamp int64) {
 64	startTimestamp := int64(0)
 65	self.UnclaimablePeriods().ReverseIterate(0, endTimestamp, func(key int64, value any) bool {
 66		v, ok := value.(int64)
 67		if !ok {
 68			panic("failed to cast value to int64")
 69		}
 70		if v != 0 {
 71			// Already ended, no need to update
 72			// keeping startTimestamp as 0 to indicate this
 73			return true
 74		}
 75		startTimestamp = key
 76		return true
 77	})
 78
 79	if startTimestamp == 0 {
 80		// No ongoing unclaimable period found
 81		return
 82	}
 83
 84	if startTimestamp == endTimestamp {
 85		self.Incentives.RemoveUnclaimablePeriod(startTimestamp)
 86	} else {
 87		self.Incentives.SetUnclaimablePeriod(startTimestamp, endTimestamp)
 88	}
 89}
 90
 91// calculate unclaimable reward by checking unclaimable periods
 92func (self *IncentivesResolver) calculateUnclaimableReward(incentiveId string) int64 {
 93	incentive, ok := self.Get(incentiveId)
 94	if !ok {
 95		return 0
 96	}
 97
 98	timeDiff := int64(0)
 99
100	// Find unclaimable periods that end before or at incentive start, reverse iterate is inclusive of the end key
101	self.UnclaimablePeriods().ReverseIterate(0, incentive.StartTimestamp()-1, func(startTimestamp int64, value any) bool {
102		endTimestamp, ok := value.(int64)
103		if !ok {
104			panic("failed to cast value to int64")
105		}
106
107		if endTimestamp == 0 {
108			endTimestamp = incentive.EndTimestamp()
109		}
110
111		if endTimestamp <= incentive.StartTimestamp() {
112			return true
113		}
114
115		// Calculate duration of unclaimable period that overlaps with incentive period
116		duration := calculateUnClaimableDuration(
117			startTimestamp,
118			endTimestamp,
119			incentive.StartTimestamp(),
120			incentive.EndTimestamp(),
121		)
122
123		timeDiff = gnsmath.SafeAddInt64(timeDiff, duration)
124
125		return true
126	})
127
128	// Find unclaimable periods that start within incentive period
129	self.UnclaimablePeriods().Iterate(incentive.StartTimestamp(), incentive.EndTimestamp(), func(startTimestamp int64, value any) bool {
130		endTimestamp, ok := value.(int64)
131		if !ok {
132			panic("failed to cast value to int64")
133		}
134
135		if endTimestamp == 0 {
136			endTimestamp = incentive.EndTimestamp()
137		}
138
139		// Calculate duration of unclaimable period that overlaps with incentive period
140		duration := calculateUnClaimableDuration(
141			startTimestamp,
142			endTimestamp,
143			incentive.StartTimestamp(),
144			incentive.EndTimestamp(),
145		)
146		timeDiff = gnsmath.SafeAddInt64(timeDiff, duration)
147
148		// ensures continue iterating through all unclaimable periods
149		return false
150	})
151
152	// rewardPerSecondX128 = rps << 128, so dividing by q128 here recovers the
153	// floor of (timeDiff * rps) without the truncation that an int64 rps would
154	// have introduced at incentive-creation time.
155	unclaimable := u256.MulDiv(
156		u256.NewUintFromInt64(timeDiff),
157		incentive.RewardPerSecondX128(),
158		q128,
159	)
160	return gnsmath.SafeConvertToInt64(unclaimable)
161}
162
163// calculateUnClaimableDuration calculates the duration of overlap between an unclaimable period and incentive period
164func calculateUnClaimableDuration(unclaimableStart, unclaimableEnd, incentiveStartTimestamp, incentiveEndTimestamp int64) int64 {
165	// Use later timestamp between unclaimable start and incentive start
166	startTime := unclaimableStart
167	if startTime < incentiveStartTimestamp {
168		startTime = incentiveStartTimestamp
169	}
170
171	// Use earlier timestamp between unclaimable end and incentive end
172	endTime := unclaimableEnd
173	if endTime > incentiveEndTimestamp {
174		endTime = incentiveEndTimestamp
175	}
176
177	// Return 0 if no overlap
178	if endTime < startTime {
179		return 0
180	}
181
182	// Calculate overlap duration
183	return gnsmath.SafeSubInt64(endTime, startTime)
184}