protocol_fee_reward_manager.gno
8.83 Kb · 264 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// updateAccumulatedProtocolFeeX128PerStake updates the internal accumulated protocol fee state.
124// This method should be called before any stake changes to ensure accurate reward calculations.
125//
126// Parameters:
127// - protocolFeeAmounts: current protocol fee amounts for all tokens
128// - currentTimestamp: current timestamp
129func (self *ProtocolFeeRewardManagerResolver) updateAccumulatedProtocolFeeX128PerStake(
130 protocolFeeAmounts map[string]int64,
131 currentTimestamp int64,
132) error {
133 // Don't update if we're looking at a past timestamp
134 if self.GetAccumulatedTimestamp() > currentTimestamp {
135 return nil
136 }
137
138 accumulatedProtocolFeeX128PerStake, changedProtocolFeeAmounts, err := self.calculateAccumulatedRewardX128PerStake(
139 protocolFeeAmounts,
140 currentTimestamp,
141 )
142 if err != nil {
143 return err
144 }
145
146 self.SetAccumulatedProtocolFeeX128PerStake(accumulatedProtocolFeeX128PerStake)
147 self.SetProtocolFeeAmounts(changedProtocolFeeAmounts)
148 self.SetAccumulatedTimestamp(currentTimestamp)
149
150 return nil
151}
152
153// addStake adds a stake for an address and updates their protocol fee reward state.
154// This method ensures rewards are properly calculated before the stake change.
155//
156// Parameters:
157// - address: staker's address
158// - amount: amount of stake to add
159// - currentTimestamp: current timestamp
160func (self *ProtocolFeeRewardManagerResolver) addStake(address string, amount int64, currentTimestamp int64) error {
161 if amount <= 0 {
162 return errors.New("amount must be positive")
163 }
164
165 rewardState, ok, err := self.GetRewardState(address)
166 if err != nil {
167 return err
168 }
169 if !ok {
170 rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
171 }
172
173 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
174
175 currentTotal := self.GetTotalStakedAmount()
176 if currentTotal > math.MaxInt64-amount {
177 return errors.New("total staked amount would overflow")
178 }
179 updatedTotalStakedAmount := gnsmath.SafeAddInt64(currentTotal, amount)
180
181 err = resolvedState.addStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
182 if err != nil {
183 return err
184 }
185
186 self.setRewardState(address, rewardState)
187 self.SetTotalStakedAmount(updatedTotalStakedAmount)
188
189 return nil
190}
191
192// removeStake removes a stake for an address and updates their protocol fee reward state.
193// This method ensures rewards are properly calculated before the stake change.
194//
195// Parameters:
196// - address: staker's address
197// - amount: amount of stake to remove
198// - currentTimestamp: current timestamp
199func (self *ProtocolFeeRewardManagerResolver) removeStake(address string, amount int64, currentTimestamp int64) error {
200 if amount < 0 {
201 return errors.New("amount must be non-negative")
202 }
203
204 rewardState, ok, err := self.GetRewardState(address)
205 if err != nil {
206 return err
207 }
208 if !ok {
209 rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
210 }
211
212 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
213 err = resolvedState.removeStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
214 if err != nil {
215 return err
216 }
217
218 self.setRewardState(address, rewardState)
219
220 updatedTotalStakedAmount := gnsmath.SafeSubInt64(self.GetTotalStakedAmount(), amount)
221 if updatedTotalStakedAmount < 0 {
222 updatedTotalStakedAmount = 0
223 }
224 self.SetTotalStakedAmount(updatedTotalStakedAmount)
225
226 return nil
227}
228
229// claimRewards processes protocol fee reward claiming for an address.
230// This method calculates and returns the amounts of rewards claimed for each token.
231//
232// Parameters:
233// - address: staker's address claiming rewards
234// - currentTimestamp: current timestamp
235//
236// Returns:
237// - map[string]int64: map of token path to claimed reward amount
238// - error: nil on success, error if claiming fails
239func (self *ProtocolFeeRewardManagerResolver) claimRewards(address string, currentTimestamp int64) (map[string]int64, error) {
240 rewardState, ok, err := self.GetRewardState(address)
241 if err != nil {
242 return nil, err
243 }
244 if !ok {
245 return make(map[string]int64), nil
246 }
247
248 resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
249 claimedRewards, err := resolvedState.claimRewardsWithUpdateRewardDebtX128(
250 self.GetAllAccumulatedProtocolFeeX128PerStake(),
251 currentTimestamp,
252 )
253 if err != nil {
254 return nil, err
255 }
256
257 self.setRewardState(address, rewardState)
258
259 return claimedRewards, nil
260}
261
262func (self *ProtocolFeeRewardManagerResolver) setRewardState(address string, rewardState *staker.ProtocolFeeRewardState) {
263 self.SetRewardState(address, rewardState)
264}