protocol_fee_reward_state.gno
9.08 Kb · 267 lines
1package staker
2
3import (
4 "errors"
5
6 "gno.land/p/gnoswap/consts"
7 gnsmath "gno.land/p/gnoswap/gnsmath"
8 u256 "gno.land/p/gnoswap/uint256"
9 "gno.land/r/gnoswap/gov/staker"
10)
11
12type ProtocolFeeRewardStateResolver struct {
13 *staker.ProtocolFeeRewardState
14}
15
16func NewProtocolFeeRewardStateResolver(protocolFeeRewardState *staker.ProtocolFeeRewardState) *ProtocolFeeRewardStateResolver {
17 return &ProtocolFeeRewardStateResolver{protocolFeeRewardState}
18}
19
20// IsClaimable checks if rewards can be claimed at the given timestamp.
21// Rewards are claimable if the current timestamp is greater than the last claimed timestamp.
22//
23// Parameters:
24// - currentTimestamp: current timestamp to check against
25//
26// Returns:
27// - bool: true if rewards can be claimed, false otherwise
28func (p *ProtocolFeeRewardStateResolver) IsClaimable(currentTimestamp int64) bool {
29 return p.GetClaimedTimestamp() < currentTimestamp
30}
31
32// GetClaimableRewardAmounts calculates the claimable reward amounts for all tokens.
33// This includes both accumulated rewards and newly earned rewards based on current state.
34//
35// Parameters:
36// - accumulatedRewardsX128PerStake: current system-wide accumulated rewards per stake for all tokens
37// - currentTimestamp: current timestamp
38//
39// Returns:
40// - map[string]int64: map of token path to claimable reward amount
41// - error: nil on success, error if claiming is not allowed
42func (p *ProtocolFeeRewardStateResolver) GetClaimableRewardAmounts(
43 accumulatedRewardsX128PerStake map[string]*u256.Uint,
44 currentTimestamp int64,
45) (map[string]int64, error) {
46 newlyEarnedRewards, err := p.calculateClaimableRewards(accumulatedRewardsX128PerStake, currentTimestamp)
47 if err != nil {
48 return nil, err
49 }
50
51 claimableRewards := make(map[string]int64)
52 accumulatedRewards := p.GetAccumulatedRewards()
53 claimedRewards := p.GetClaimedRewards()
54
55 for token, accumulatedReward := range accumulatedRewards {
56 claimableRewards[token] = gnsmath.SafeSubInt64(accumulatedReward, claimedRewards[token])
57 }
58
59 for token, newlyEarnedReward := range newlyEarnedRewards {
60 claimableRewards[token] = gnsmath.SafeAddInt64(claimableRewards[token], newlyEarnedReward)
61 }
62
63 return claimableRewards, nil
64}
65
66// calculateClaimableRewards calculates newly earned rewards for all tokens since the last update.
67// This method uses the difference between current and stored reward debt to calculate earnings.
68//
69// Parameters:
70// - accumulatedRewardsX128PerStake: current system-wide accumulated rewards per stake for all tokens
71// - currentTimestamp: current timestamp
72//
73// Returns:
74// - map[string]int64: map of token path to newly earned reward amount
75func (p *ProtocolFeeRewardStateResolver) calculateClaimableRewards(
76 accumulatedRewardsX128PerStake map[string]*u256.Uint,
77 currentTimestamp int64,
78) (map[string]int64, error) {
79 // Don't calculate rewards for past timestamps
80 if p.GetAccumulatedTimestamp() >= currentTimestamp {
81 return make(map[string]int64), nil
82 }
83
84 rewardAmounts := make(map[string]int64)
85 stakedAmount := p.GetStakedAmount()
86
87 // Calculate rewards for each token type
88 for token, accumulatedRewardX128PerStake := range accumulatedRewardsX128PerStake {
89 // Get reward debt for this token
90 rewardDebtX128 := p.GetRewardDebtX128ForToken(token)
91 if rewardDebtX128 == nil {
92 rewardDebtX128 = u256.Zero()
93 }
94
95 // Calculate the difference in accumulated rewards per stake since last update
96 // Using modular arithmetic for accumulator values - underflow is allowed and handled correctly
97 rewardDebtDeltaX128 := u256.Zero().Sub(
98 accumulatedRewardX128PerStake,
99 rewardDebtX128,
100 )
101
102 // Multiply by staked amount to get total reward for this staker and token
103 rewardAmount := u256.MulDiv(
104 rewardDebtDeltaX128,
105 u256.NewUintFromInt64(stakedAmount),
106 consts.Q128(),
107 )
108
109 rewardAmounts[token] = gnsmath.SafeConvertToInt64(rewardAmount)
110 }
111
112 return rewardAmounts, nil
113}
114
115// addStake increases the staked amount for this address.
116// This method should be called when a user increases their stake.
117//
118// Parameters:
119// - amount: amount of stake to add
120func (p *ProtocolFeeRewardStateResolver) addStake(amount int64) {
121 p.SetStakedAmount(gnsmath.SafeAddInt64(p.GetStakedAmount(), amount))
122}
123
124// removeStake decreases the staked amount for this address.
125// This method should be called when a user decreases their stake.
126//
127// Parameters:
128// - amount: amount of stake to remove
129func (p *ProtocolFeeRewardStateResolver) removeStake(amount int64) {
130 newAmount := gnsmath.SafeSubInt64(p.GetStakedAmount(), amount)
131 if newAmount < 0 {
132 newAmount = 0
133 }
134 p.SetStakedAmount(newAmount)
135}
136
137// claimRewards processes reward claiming for all tokens and updates the claim state.
138// This method validates claimability and transfers accumulated rewards to claimed status.
139//
140// Parameters:
141// - currentTimestamp: current timestamp
142//
143// Returns:
144// - map[string]int64: map of token path to claimed reward amount
145// - error: nil on success, error if reward debt is stale
146func (p *ProtocolFeeRewardStateResolver) claimRewards(currentTimestamp int64) (map[string]int64, error) {
147 if !p.IsClaimable(currentTimestamp) {
148 return make(map[string]int64), nil
149 }
150
151 if p.GetAccumulatedTimestamp() < currentTimestamp {
152 return nil, errors.New("must update reward debt before claiming rewards")
153 }
154
155 currentClaimedRewards := make(map[string]int64)
156 accumulatedRewards := p.GetAccumulatedRewards()
157 claimedRewards := p.GetClaimedRewards()
158
159 // Calculate and update claimed amounts for each token
160 for token, rewardAmount := range accumulatedRewards {
161 claimedAmount := claimedRewards[token]
162 currentClaimedRewards[token] = gnsmath.SafeSubInt64(rewardAmount, claimedAmount)
163 p.SetClaimedRewardForToken(token, rewardAmount)
164 }
165
166 p.SetClaimedTimestamp(currentTimestamp)
167
168 return currentClaimedRewards, nil
169}
170
171// updateRewardDebtX128 updates the reward debt and accumulates new rewards for all tokens.
172// This method should be called before any stake changes to ensure accurate reward tracking.
173//
174// Parameters:
175// - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake for all tokens
176// - currentTimestamp: current timestamp
177func (p *ProtocolFeeRewardStateResolver) updateRewardDebtX128(
178 accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
179 currentTimestamp int64,
180) error {
181 // Don't update if we're looking at a past timestamp
182 if p.GetAccumulatedTimestamp() >= currentTimestamp {
183 return nil
184 }
185
186 // Calculate and accumulate new rewards for all tokens
187 rewardAmounts, err := p.calculateClaimableRewards(accumulatedProtocolFeeX128PerStake, currentTimestamp)
188 if err != nil {
189 return err
190 }
191
192 // Update reward debt for all tokens
193 p.SetRewardDebtX128(accumulatedProtocolFeeX128PerStake)
194
195 // Add newly calculated rewards to accumulated amounts
196 accumulatedRewards := p.GetAccumulatedRewards()
197 for token, rewardAmount := range rewardAmounts {
198 p.SetAccumulatedRewardForToken(token, gnsmath.SafeAddInt64(accumulatedRewards[token], rewardAmount))
199 }
200
201 p.SetAccumulatedTimestamp(currentTimestamp)
202
203 return nil
204}
205
206// addStakeWithUpdateRewardDebtX128 adds stake and updates reward debt in one operation.
207// This ensures rewards are properly calculated before the stake change takes effect.
208//
209// Parameters:
210// - amount: amount of stake to add
211// - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
212// - currentTimestamp: current timestamp
213func (p *ProtocolFeeRewardStateResolver) addStakeWithUpdateRewardDebtX128(
214 amount int64,
215 accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
216 currentTimestamp int64,
217) error {
218 err := p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
219 if err != nil {
220 return err
221 }
222
223 p.addStake(amount)
224
225 return nil
226}
227
228// removeStakeWithUpdateRewardDebtX128 removes stake and updates reward debt in one operation.
229// This ensures rewards are properly calculated before the stake change takes effect.
230//
231// Parameters:
232// - amount: amount of stake to remove
233// - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
234// - currentTimestamp: current timestamp
235func (p *ProtocolFeeRewardStateResolver) removeStakeWithUpdateRewardDebtX128(
236 amount int64,
237 accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
238 currentTimestamp int64,
239) error {
240 err := p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
241 if err != nil {
242 return err
243 }
244
245 p.removeStake(amount)
246
247 return nil
248}
249
250// claimRewardsWithUpdateRewardDebtX128 claims rewards and updates reward debt in one operation.
251// This ensures all rewards are properly calculated before claiming.
252//
253// Parameters:
254// - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
255// - currentTimestamp: current timestamp
256//
257// Returns:
258// - map[string]int64: map of token path to claimed reward amount
259// - error: nil on success, error if claiming fails
260func (p *ProtocolFeeRewardStateResolver) claimRewardsWithUpdateRewardDebtX128(
261 accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
262 currentTimestamp int64,
263) (map[string]int64, error) {
264 p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
265
266 return p.claimRewards(currentTimestamp)
267}