emission_reward_state.gno
7.85 Kb · 231 lines
1package staker
2
3import (
4 "gno.land/p/gnoswap/consts"
5 gnsmath "gno.land/p/gnoswap/gnsmath"
6 u256 "gno.land/p/gnoswap/uint256"
7 "gno.land/r/gnoswap/gov/staker"
8)
9
10type EmissionRewardStateResolver struct {
11 *staker.EmissionRewardState
12}
13
14func NewEmissionRewardStateResolver(emissionRewardState *staker.EmissionRewardState) *EmissionRewardStateResolver {
15 return &EmissionRewardStateResolver{emissionRewardState}
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 (self *EmissionRewardStateResolver) IsClaimable(currentTimestamp int64) bool {
27 return self.GetClaimedTimestamp() < currentTimestamp
28}
29
30// GetClaimableRewardAmount calculates the total amount of rewards that can be claimed.
31// This includes both accumulated rewards and newly earned rewards based on current state.
32//
33// Parameters:
34// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
35// - currentTimestamp: current timestamp
36//
37// Returns:
38// - int64: total claimable reward amount
39func (self *EmissionRewardStateResolver) GetClaimableRewardAmount(
40 accumulatedRewardX128PerStake *u256.Uint,
41 currentTimestamp int64,
42) (int64, error) {
43 rewardAmount, err := self.calculateClaimableRewards(accumulatedRewardX128PerStake, currentTimestamp)
44 if err != nil {
45 return 0, err
46 }
47
48 accumulatedUnclaimedReward := gnsmath.SafeSubInt64(
49 self.GetAccumulatedRewardAmount(),
50 self.GetClaimedRewardAmount(),
51 )
52
53 return gnsmath.SafeAddInt64(accumulatedUnclaimedReward, rewardAmount), nil
54}
55
56// calculateClaimableRewards calculates newly earned rewards since the last update.
57// Uses the difference between current and stored reward debt to calculate earnings.
58//
59// Parameters:
60// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
61// - currentTimestamp: current timestamp
62//
63// Returns:
64// - int64: newly earned reward amount since last update
65// - error: nil on success, error if calculation fails
66func (self *EmissionRewardStateResolver) calculateClaimableRewards(
67 accumulatedRewardX128PerStake *u256.Uint,
68 currentTimestamp int64,
69) (int64, error) {
70 stakedAmount := self.GetStakedAmount()
71
72 // Don't calculate rewards for past timestamps or when nothing is staked
73 if currentTimestamp < self.GetAccumulatedTimestamp() || stakedAmount == 0 {
74 return 0, nil
75 }
76
77 // Calculate the difference in accumulated rewards per stake since last update
78 // Using modular arithmetic for accumulator values - underflow is allowed and handled correctly
79 rewardDebtDeltaX128 := u256.Zero().Sub(
80 accumulatedRewardX128PerStake,
81 self.GetRewardDebtX128(),
82 )
83
84 // Calculate reward amount by multiplying reward debt delta by staked amount and dividing by Q128
85 // rewardAmount = (rewardDebtDeltaX128 * stakedAmount) / Q128
86 rewardAmount := u256.MulDiv(
87 rewardDebtDeltaX128,
88 u256.NewUintFromInt64(stakedAmount),
89 consts.Q128(),
90 )
91 return gnsmath.SafeConvertToInt64(rewardAmount), nil
92}
93
94// addStake increases the staked amount for this address.
95// This method should be called when a user increases their stake.
96//
97// Parameters:
98// - amount: amount of stake to add
99func (self *EmissionRewardStateResolver) addStake(amount int64) {
100 self.adjustStake(amount)
101}
102
103// removeStake decreases the staked amount for this address.
104// This method should be called when a user decreases their stake.
105//
106// Parameters:
107// - amount: amount of stake to remove
108func (self *EmissionRewardStateResolver) removeStake(amount int64) {
109 self.adjustStake(-amount)
110}
111
112// adjustStake is a small internal helper to centralize bound checks and math.
113func (self *EmissionRewardStateResolver) adjustStake(delta int64) {
114 if delta == 0 {
115 return
116 }
117 // clamp at zero on underflow
118 newAmt := gnsmath.SafeAddInt64(self.GetStakedAmount(), delta)
119 if newAmt < 0 {
120 newAmt = 0
121 }
122 self.SetStakedAmount(newAmt)
123}
124
125// claimRewards processes reward claiming and updates the claim state.
126// This method validates claimability and transfers accumulated rewards to claimed status.
127//
128// Parameters:
129// - currentTimestamp: current timestamp
130//
131// Returns:
132// - int64: amount of rewards claimed (0 if already claimed at this timestamp)
133// - error: always nil in the current implementation
134func (self *EmissionRewardStateResolver) claimRewards(currentTimestamp int64) (int64, error) {
135 if !self.IsClaimable(currentTimestamp) {
136 return 0, nil
137 }
138
139 accumulatedRewardAmount := self.GetAccumulatedRewardAmount()
140 previousClaimedAmount := self.GetClaimedRewardAmount()
141 claimableAmount := gnsmath.SafeSubInt64(accumulatedRewardAmount, previousClaimedAmount)
142
143 self.SetClaimedRewardAmount(accumulatedRewardAmount)
144 self.SetClaimedTimestamp(currentTimestamp)
145
146 return claimableAmount, nil
147}
148
149// updateRewardDebtX128 updates the reward debt and accumulates new rewards.
150// This method should be called before any stake changes to ensure accurate reward tracking.
151//
152// Parameters:
153// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
154// - currentTimestamp: current timestamp
155func (self *EmissionRewardStateResolver) updateRewardDebtX128(
156 accumulatedRewardX128PerStake *u256.Uint,
157 currentTimestamp int64,
158) error {
159 rewardAmount, err := self.calculateClaimableRewards(accumulatedRewardX128PerStake, currentTimestamp)
160 if err != nil {
161 return err
162 }
163
164 // Accumulate newly earned rewards
165 if rewardAmount != 0 {
166 self.SetAccumulatedRewardAmount(gnsmath.SafeAddInt64(self.GetAccumulatedRewardAmount(), rewardAmount))
167 }
168
169 // Deep copy to avoid aliasing with external state
170 self.SetRewardDebtX128(accumulatedRewardX128PerStake.Clone())
171 self.SetAccumulatedTimestamp(currentTimestamp)
172 return nil
173}
174
175// addStakeWithUpdateRewardDebtX128 adds stake and updates reward debt in one operation.
176// This ensures rewards are properly calculated before the stake change takes effect.
177//
178// Parameters:
179// - amount: amount of stake to add
180// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
181// - currentTimestamp: current timestamp
182func (self *EmissionRewardStateResolver) addStakeWithUpdateRewardDebtX128(
183 amount int64,
184 accumulatedRewardX128PerStake *u256.Uint,
185 currentTimestamp int64,
186) error {
187 if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
188 return err
189 }
190 self.addStake(amount)
191 return nil
192}
193
194// removeStakeWithUpdateRewardDebtX128 removes stake and updates reward debt in one operation.
195// This ensures rewards are properly calculated before the stake change takes effect.
196//
197// Parameters:
198// - amount: amount of stake to remove
199// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
200// - currentTimestamp: current timestamp
201func (self *EmissionRewardStateResolver) removeStakeWithUpdateRewardDebtX128(
202 amount int64,
203 accumulatedRewardX128PerStake *u256.Uint,
204 currentTimestamp int64,
205) error {
206 if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
207 return err
208 }
209 self.removeStake(amount)
210 return nil
211}
212
213// claimRewardsWithUpdateRewardDebtX128 claims rewards and updates reward debt in one operation.
214// This ensures all rewards are properly calculated before claiming.
215//
216// Parameters:
217// - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
218// - currentTimestamp: current timestamp
219//
220// Returns:
221// - int64: amount of rewards claimed
222// - error: nil on success, error if claiming fails
223func (self *EmissionRewardStateResolver) claimRewardsWithUpdateRewardDebtX128(
224 accumulatedRewardX128PerStake *u256.Uint,
225 currentTimestamp int64,
226) (int64, error) {
227 if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
228 return 0, err
229 }
230 return self.claimRewards(currentTimestamp)
231}