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}