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}