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