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