Search Apps Documentation Source Content File Folder Download Copy Actions Download

reward_calculation_incentives.gno

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