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