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