protocol_fee_reward_manager.gno
12.42 Kb · 358 lines
1package staker
2
3import (
4 "errors"
5 "math"
6
7 gnsmath "gno.land/p/gnoswap/gnsmath"
8 u256 "gno.land/p/gnoswap/uint256"
9 "gno.land/r/gnoswap/gov/staker"
10)
11
12type ProtocolFeeRewardManagerResolver struct {
13 *staker.ProtocolFeeRewardManager
14}
15
16func NewProtocolFeeRewardManagerResolver(manager *staker.ProtocolFeeRewardManager) *ProtocolFeeRewardManagerResolver {
17 return &ProtocolFeeRewardManagerResolver{manager}
18}
19
20// GetClaimableRewardAmounts calculates the claimable reward amounts for all tokens for a specific address.
21// This method computes rewards based on current protocol fee distribution state and staking history.
22//
23// Parameters:
24// - protocolFeeAmounts: current protocol fee amounts for all tokens
25// - address: staker's address to calculate rewards for
26// - currentTimestamp: current timestamp
27//
28// Returns:
29// - map[string]int64: map of token path to claimable reward amount
30func (self *ProtocolFeeRewardManagerResolver) GetClaimableRewardAmounts(
31 protocolFeeAmounts map[string]int64,
32 address string,
33 currentTimestamp int64,
34) (map[string]int64, error) {
35 rewardState, ok, err := self.GetRewardState(address)
36 if err != nil {
37 return nil, err
38 }
39 if !ok {
40 return make(map[string]int64), nil
41 }
42
43 accumulatedRewardX128PerStake, _, err := self.calculateAccumulatedRewardX128PerStake(
44 protocolFeeAmounts,
45 currentTimestamp,
46 )
47 if err != nil {
48 return nil, err
49 }
50
51 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
52
53 return resolvedState.GetClaimableRewardAmounts(accumulatedRewardX128PerStake, currentTimestamp)
54}
55
56// calculateAccumulatedRewardX128PerStake calculates the updated accumulated reward per stake for all tokens.
57// This method computes new accumulated reward rates based on newly distributed protocol fees.
58//
59// Parameters:
60// - protocolFeeAmounts: current protocol fee amounts for all tokens
61// - currentTimestamp: current timestamp
62//
63// Returns:
64// - map[string]*u256.Uint: updated accumulated reward per stake for each token
65// - map[string]int64: updated protocol fee amounts for each token
66func (self *ProtocolFeeRewardManagerResolver) calculateAccumulatedRewardX128PerStake(
67 protocolFeeAmounts map[string]int64,
68 currentTimestamp int64,
69) (map[string]*u256.Uint, map[string]int64, error) {
70 // If we're looking at a past timestamp, return current state
71 if self.GetAccumulatedTimestamp() > currentTimestamp {
72 return self.GetAllAccumulatedProtocolFeeX128PerStake(), self.GetProtocolFeeAmounts(), nil
73 }
74
75 accumulatedProtocolFeesX128PerStake := make(map[string]*u256.Uint)
76 changedProtocolFeeAmounts := make(map[string]int64)
77
78 // Process each token's protocol fees
79 for token, protocolFeeAmount := range protocolFeeAmounts {
80 previousProtocolFeeAmount := self.GetProtocolFeeAmount(token)
81
82 protocolFeeDelta := gnsmath.SafeSubInt64(protocolFeeAmount, previousProtocolFeeAmount)
83
84 // If no new fees for this token, keep existing rate
85 if protocolFeeDelta <= 0 {
86 accumulatedProtocolFeesX128PerStake[token] = self.GetAccumulatedProtocolFeeX128PerStake(token)
87 if accumulatedProtocolFeesX128PerStake[token] == nil {
88 accumulatedProtocolFeesX128PerStake[token] = u256.NewUint(0)
89 }
90 changedProtocolFeeAmounts[token] = protocolFeeAmount
91 continue
92 }
93
94 // Scale the fee delta by 2^128 for precision
95 protocolFeeDeltaX128 := u256.NewUintFromInt64(protocolFeeDelta)
96 protocolFeeDeltaX128 = u256.Zero().Lsh(protocolFeeDeltaX128, 128)
97
98 protocolFeeDeltaX128PerStake := u256.Zero()
99
100 // Calculate fee per stake if there are staked tokens
101 if self.GetTotalStakedAmount() > 0 {
102 feePerStake := u256.Zero().Div(protocolFeeDeltaX128, u256.NewUintFromInt64(self.GetTotalStakedAmount()))
103 protocolFeeDeltaX128PerStake = feePerStake
104 }
105
106 // Get current accumulated fee per stake for this token
107 accumulatedProtocolFeeX128PerStake := u256.Zero()
108 existingAccumulatedFee := self.GetAccumulatedProtocolFeeX128PerStake(token)
109 if existingAccumulatedFee != nil {
110 accumulatedProtocolFeeX128PerStake = existingAccumulatedFee
111 }
112
113 // Add the new fee per stake to the accumulated amount
114 accumulatedProtocolFeeX128PerStake = u256.Zero().Add(accumulatedProtocolFeeX128PerStake, protocolFeeDeltaX128PerStake)
115 accumulatedProtocolFeesX128PerStake[token] = accumulatedProtocolFeeX128PerStake.Clone()
116
117 changedProtocolFeeAmounts[token] = protocolFeeAmount
118 }
119
120 return accumulatedProtocolFeesX128PerStake, changedProtocolFeeAmounts, nil
121}
122
123// calculateAccumulatedRewardX128PerStakeForToken calculates accumulated reward per stake for exactly one token.
124func (self *ProtocolFeeRewardManagerResolver) calculateAccumulatedRewardX128PerStakeForToken(
125 tokenPath string,
126 protocolFeeAmount int64,
127) (*u256.Uint, int64, error) {
128 previousProtocolFeeAmount := self.GetProtocolFeeAmount(tokenPath)
129 protocolFeeDelta := gnsmath.SafeSubInt64(protocolFeeAmount, previousProtocolFeeAmount)
130
131 existingAccumulatedFee := self.GetAccumulatedProtocolFeeX128PerStake(tokenPath)
132 if existingAccumulatedFee == nil {
133 existingAccumulatedFee = u256.Zero()
134 }
135
136 if protocolFeeDelta <= 0 {
137 return existingAccumulatedFee.Clone(), protocolFeeAmount, nil
138 }
139
140 protocolFeeDeltaX128 := u256.NewUintFromInt64(protocolFeeDelta)
141 protocolFeeDeltaX128 = u256.Zero().Lsh(protocolFeeDeltaX128, 128)
142
143 protocolFeeDeltaX128PerStake := u256.Zero()
144 if self.GetTotalStakedAmount() > 0 {
145 protocolFeeDeltaX128PerStake = u256.Zero().Div(protocolFeeDeltaX128, u256.NewUintFromInt64(self.GetTotalStakedAmount()))
146 }
147
148 accumulatedProtocolFeeX128PerStake := u256.Zero().Add(existingAccumulatedFee, protocolFeeDeltaX128PerStake)
149
150 return accumulatedProtocolFeeX128PerStake.Clone(), protocolFeeAmount, nil
151}
152
153// updateAccumulatedProtocolFeeX128PerStake updates the internal accumulated protocol fee state.
154// This method should be called before any stake changes to ensure accurate reward calculations.
155//
156// Parameters:
157// - protocolFeeAmounts: current protocol fee amounts for all tokens
158// - currentTimestamp: current timestamp
159func (self *ProtocolFeeRewardManagerResolver) updateAccumulatedProtocolFeeX128PerStake(
160 protocolFeeAmounts map[string]int64,
161 currentTimestamp int64,
162) error {
163 // Don't update if we're looking at a past timestamp
164 if self.GetAccumulatedTimestamp() > currentTimestamp {
165 return nil
166 }
167
168 accumulatedProtocolFeeX128PerStake, changedProtocolFeeAmounts, err := self.calculateAccumulatedRewardX128PerStake(
169 protocolFeeAmounts,
170 currentTimestamp,
171 )
172 if err != nil {
173 return err
174 }
175
176 // Persist only the tokens whose value actually changed instead of replacing the whole map.
177 // calculateAccumulatedRewardX128PerStake returns the previous (identical) accumulator and amount for
178 // tokens that received no new fees this round, so the full-map setters would deep-copy and re-persist
179 // every registered token on every add/remove/claim, growing storage and gas linearly in the number of
180 // registered tokens regardless of how many actually earned fees (gas report issue 1 / §1.2).
181 //
182 // Registered protocol-fee tokens are monotonic (never removed) and distributedAmounts always covers the
183 // full registered set, so the stored token set never shrinks; skipping unchanged entries therefore yields
184 // the same persisted state as the previous full-replace, only without the redundant re-writes.
185 for token, newAcc := range accumulatedProtocolFeeX128PerStake {
186 existing := self.GetAccumulatedProtocolFeeX128PerStake(token)
187 if existing == nil || !existing.Eq(newAcc) {
188 self.SetAccumulatedProtocolFeeX128PerStakeForToken(token, newAcc)
189 }
190 }
191 for token, amount := range changedProtocolFeeAmounts {
192 if self.GetProtocolFeeAmount(token) != amount {
193 self.SetProtocolFeeAmountForToken(token, amount)
194 }
195 }
196
197 self.SetAccumulatedTimestamp(currentTimestamp)
198
199 return nil
200}
201
202// updateAccumulatedProtocolFeeX128PerStakeForToken updates manager accumulators for exactly one token.
203func (self *ProtocolFeeRewardManagerResolver) updateAccumulatedProtocolFeeX128PerStakeForToken(
204 tokenPath string,
205 protocolFeeAmount int64,
206 currentTimestamp int64,
207) error {
208 accumulatedProtocolFeeX128PerStake, changedProtocolFeeAmount, err := self.calculateAccumulatedRewardX128PerStakeForToken(
209 tokenPath,
210 protocolFeeAmount,
211 )
212 if err != nil {
213 return err
214 }
215
216 self.SetAccumulatedProtocolFeeX128PerStakeForToken(tokenPath, accumulatedProtocolFeeX128PerStake)
217 self.SetProtocolFeeAmountForToken(tokenPath, changedProtocolFeeAmount)
218
219 return nil
220}
221
222// addStake adds a stake for an address and updates their protocol fee reward state.
223// This method ensures rewards are properly calculated before the stake change.
224//
225// Parameters:
226// - address: staker's address
227// - amount: amount of stake to add
228// - currentTimestamp: current timestamp
229func (self *ProtocolFeeRewardManagerResolver) addStake(address string, amount int64, currentTimestamp int64) error {
230 if amount <= 0 {
231 return errors.New("amount must be positive")
232 }
233
234 rewardState, ok, err := self.GetRewardState(address)
235 if err != nil {
236 return err
237 }
238 if !ok {
239 rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
240 }
241
242 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
243
244 currentTotal := self.GetTotalStakedAmount()
245 if currentTotal > math.MaxInt64-amount {
246 return errors.New("total staked amount would overflow")
247 }
248 updatedTotalStakedAmount := gnsmath.SafeAddInt64(currentTotal, amount)
249
250 err = resolvedState.addStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
251 if err != nil {
252 return err
253 }
254
255 self.setRewardState(address, rewardState)
256 self.SetTotalStakedAmount(updatedTotalStakedAmount)
257
258 return nil
259}
260
261// removeStake removes a stake for an address and updates their protocol fee reward state.
262// This method ensures rewards are properly calculated before the stake change.
263//
264// Parameters:
265// - address: staker's address
266// - amount: amount of stake to remove
267// - currentTimestamp: current timestamp
268func (self *ProtocolFeeRewardManagerResolver) removeStake(address string, amount int64, currentTimestamp int64) error {
269 if amount < 0 {
270 return errors.New("amount must be non-negative")
271 }
272
273 rewardState, ok, err := self.GetRewardState(address)
274 if err != nil {
275 return err
276 }
277 if !ok {
278 rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
279 }
280
281 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
282 err = resolvedState.removeStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
283 if err != nil {
284 return err
285 }
286
287 self.setRewardState(address, rewardState)
288
289 updatedTotalStakedAmount := gnsmath.SafeSubInt64(self.GetTotalStakedAmount(), amount)
290 if updatedTotalStakedAmount < 0 {
291 updatedTotalStakedAmount = 0
292 }
293 self.SetTotalStakedAmount(updatedTotalStakedAmount)
294
295 return nil
296}
297
298// claimRewards processes protocol fee reward claiming for an address.
299// This method calculates and returns the amounts of rewards claimed for each token.
300//
301// Parameters:
302// - address: staker's address claiming rewards
303// - currentTimestamp: current timestamp
304//
305// Returns:
306// - map[string]int64: map of token path to claimed reward amount
307// - error: nil on success, error if claiming fails
308func (self *ProtocolFeeRewardManagerResolver) claimRewards(address string, currentTimestamp int64) (map[string]int64, error) {
309 rewardState, ok, err := self.GetRewardState(address)
310 if err != nil {
311 return nil, err
312 }
313 if !ok {
314 return make(map[string]int64), nil
315 }
316
317 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
318 claimedRewards, err := resolvedState.claimRewardsWithUpdateRewardDebtX128(
319 self.GetAllAccumulatedProtocolFeeX128PerStake(),
320 currentTimestamp,
321 )
322 if err != nil {
323 return nil, err
324 }
325
326 self.setRewardState(address, rewardState)
327
328 return claimedRewards, nil
329}
330
331// claimRewardForToken processes protocol fee reward claiming for exactly one token.
332func (self *ProtocolFeeRewardManagerResolver) claimRewardForToken(address string, tokenPath string, currentTimestamp int64) (int64, error) {
333 rewardState, ok, err := self.GetRewardState(address)
334 if err != nil {
335 return 0, err
336 }
337 if !ok {
338 return 0, nil
339 }
340
341 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
342 claimedReward, err := resolvedState.claimRewardForTokenWithUpdateRewardDebtX128(
343 tokenPath,
344 self.GetAccumulatedProtocolFeeX128PerStake(tokenPath),
345 currentTimestamp,
346 )
347 if err != nil {
348 return 0, err
349 }
350
351 self.setRewardState(address, rewardState)
352
353 return claimedReward, nil
354}
355
356func (self *ProtocolFeeRewardManagerResolver) setRewardState(address string, rewardState *staker.ProtocolFeeRewardState) {
357 self.SetRewardState(address, rewardState)
358}