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