emission_reward_manager.gno
7.85 Kb · 232 lines
1package staker
2
3import (
4 "errors"
5 "math"
6
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 EmissionRewardManagerResolver struct {
13 *staker.EmissionRewardManager
14}
15
16// NewEmissionRewardManagerResolver wraps an emission reward manager with reward
17// calculation and stake-accounting helpers.
18//
19// Parameters:
20// - emissionRewardManager: manager state to resolve and mutate.
21//
22// Returns:
23// - *EmissionRewardManagerResolver: resolver backed by emissionRewardManager.
24func NewEmissionRewardManagerResolver(emissionRewardManager *staker.EmissionRewardManager) *EmissionRewardManagerResolver {
25 return &EmissionRewardManagerResolver{emissionRewardManager}
26}
27
28// GetClaimableRewardAmount calculates the claimable reward amount for a specific address.
29//
30// Parameters:
31// - currentDistributedAmount: total emission amount distributed to the staker
32// manager at the current observation point.
33// - address: reward-state identifier whose claimable amount is requested.
34// - currentTimestamp: Unix timestamp used to settle the address's reward state.
35//
36// Returns:
37// - int64: emission reward currently claimable by address, or zero when no
38// reward state exists.
39// - error: nil on success; an error from accumulated-reward or reward-state
40// resolution when calculation fails.
41func (self *EmissionRewardManagerResolver) GetClaimableRewardAmount(
42 currentDistributedAmount int64,
43 address string,
44 currentTimestamp int64,
45) (int64, error) {
46 rewardState, ok, err := self.GetRewardState(address)
47 if !ok {
48 return 0, nil
49 }
50
51 accumulatedRewardX128PerStake, err := self.calculateAccumulatedRewardX128PerStake(
52 currentDistributedAmount,
53 currentTimestamp,
54 )
55 if err != nil {
56 return 0, err
57 }
58
59 resolvedState := NewEmissionRewardStateResolver(rewardState)
60 return resolvedState.GetClaimableRewardAmount(accumulatedRewardX128PerStake, currentTimestamp)
61}
62
63// calculateAccumulatedRewardX128PerStake calculates the updated accumulated reward per stake.
64func (self *EmissionRewardManagerResolver) calculateAccumulatedRewardX128PerStake(
65 currentDistributedAmount int64,
66 currentTimestamp int64,
67) (*u256.Uint, error) {
68 // If we're looking at a past timestamp, return current state
69 if currentTimestamp < self.GetAccumulatedTimestamp() {
70 return self.GetAccumulatedRewardX128PerStake(), nil
71 }
72
73 // With no stake there is nobody to credit, so the delta accrued over that window is
74 // dropped and the caller advances the cursor past it. The protocol fee accumulator
75 // applies the same policy, see ProtocolFeeRewardManagerResolver.
76 totalStakedAmount := self.GetTotalStakedAmount()
77 if totalStakedAmount == 0 {
78 return self.GetAccumulatedRewardX128PerStake(), nil
79 }
80
81 // Newly distributed rewards since last update
82 distributedAmountDelta := gnsmath.SafeSubInt64(currentDistributedAmount, self.GetDistributedAmount())
83 if distributedAmountDelta <= 0 {
84 // Non-positive delta. nothing to do more.
85 return self.GetAccumulatedRewardX128PerStake(), nil
86 }
87
88 // Reward per stake for the new distribution
89 distributedAmountDeltaX128PerStake := u256.Zero().Div(
90 u256.Zero().Lsh(u256.NewUintFromInt64(distributedAmountDelta), 128),
91 u256.NewUintFromInt64(totalStakedAmount),
92 )
93
94 // Add to accumulated reward per stake
95 accumulatedReward := u256.Zero().Add(self.GetAccumulatedRewardX128PerStake(), distributedAmountDeltaX128PerStake)
96 return accumulatedReward, nil
97}
98
99// updateAccumulatedRewardX128PerStake updates the internal accumulated reward state.
100// This method should be called before any stake changes to ensure accurate reward calculations.
101// Updates accumulated reward per stake with current distribution data.
102func (self *EmissionRewardManagerResolver) updateAccumulatedRewardX128PerStake(
103 currentDistributedAmount int64,
104 currentTimestamp int64,
105) error {
106 // DO NOT apply out-of-order timestamps
107 if currentTimestamp < self.GetAccumulatedTimestamp() {
108 return nil
109 }
110
111 // Update accumulated reward state
112 accumulatedRewardX128PerStake, err := self.calculateAccumulatedRewardX128PerStake(
113 currentDistributedAmount,
114 currentTimestamp,
115 )
116 if err != nil {
117 return err
118 }
119
120 self.setAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake.Clone())
121 self.setDistributedAmount(currentDistributedAmount)
122 self.setAccumulatedTimestamp(currentTimestamp)
123
124 return nil
125}
126
127// addStake adds a stake for an address and updates their reward state.
128// This method ensures rewards are properly calculated before the stake change.
129// Adds stake for specified address and updates reward calculations.
130func (self *EmissionRewardManagerResolver) addStake(address string, amount int64, currentTimestamp int64) error {
131 if amount < 0 {
132 return errors.New(errAmountMustBeNonNegative)
133 }
134
135 accumulatedReward := self.GetAccumulatedRewardX128PerStake()
136
137 rewardState, ok, err := self.GetRewardState(address)
138 if err != nil {
139 return err
140 }
141 if !ok {
142 rewardState = staker.NewEmissionRewardState(accumulatedReward)
143 }
144
145 resolvedState := NewEmissionRewardStateResolver(rewardState)
146 err = resolvedState.addStakeWithUpdateRewardDebtX128(amount, accumulatedReward, currentTimestamp)
147 if err != nil {
148 return err
149 }
150
151 self.setRewardStates(address, rewardState)
152
153 currentTotal := self.GetTotalStakedAmount()
154 if amount > 0 && currentTotal > math.MaxInt64-amount {
155 return errors.New(errTotalStakedAmountOverflow)
156 }
157 self.SetTotalStakedAmount(gnsmath.SafeAddInt64(currentTotal, amount))
158 return nil
159}
160
161// removeStake removes a stake for an address and updates their reward state.
162// This method ensures rewards are properly calculated before the stake change.
163// Removes stake for specified address and updates reward calculations.
164func (self *EmissionRewardManagerResolver) removeStake(address string, amount int64, currentTimestamp int64) error {
165 if amount < 0 {
166 return errors.New(errAmountMustBeNonNegative)
167 }
168
169 updatedTotalStakedAmount := gnsmath.SafeSubInt64(self.GetTotalStakedAmount(), amount)
170 if updatedTotalStakedAmount < 0 {
171 return errors.New(errRemoveAmountExceedsTotalStaked)
172 }
173
174 accumulatedReward := self.GetAccumulatedRewardX128PerStake()
175
176 rewardState, ok, err := self.GetRewardState(address)
177 if err != nil {
178 return err
179 }
180 if !ok {
181 rewardState = staker.NewEmissionRewardState(accumulatedReward)
182 }
183
184 resolvedState := NewEmissionRewardStateResolver(rewardState)
185
186 err = resolvedState.removeStakeWithUpdateRewardDebtX128(amount, accumulatedReward, currentTimestamp)
187 if err != nil {
188 return err
189 }
190
191 // persist updated state
192 self.setRewardStates(address, rewardState)
193
194 self.SetTotalStakedAmount(updatedTotalStakedAmount)
195
196 return nil
197}
198
199// claimRewards processes reward claiming for an address.
200// This method calculates and returns the amount of rewards claimed.
201// Claims available rewards for specified address.
202func (self *EmissionRewardManagerResolver) claimRewards(address string, currentTimestamp int64) (claimedRewardAmount int64, err error) {
203 rewardState, ok, err := self.GetRewardState(address)
204 if err != nil || !ok {
205 return 0, err
206 }
207
208 resolvedState := NewEmissionRewardStateResolver(rewardState)
209 claimedRewardAmount, cErr := resolvedState.claimRewardsWithUpdateRewardDebtX128(self.GetAccumulatedRewardX128PerStake(), currentTimestamp)
210 if cErr != nil {
211 return 0, cErr
212 }
213
214 self.setRewardStates(address, rewardState)
215 return claimedRewardAmount, nil
216}
217
218func (self *EmissionRewardManagerResolver) setRewardStates(address string, rewardState *staker.EmissionRewardState) {
219 self.SetRewardState(address, rewardState)
220}
221
222func (self *EmissionRewardManagerResolver) setAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake *u256.Uint) {
223 self.SetAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake)
224}
225
226func (self *EmissionRewardManagerResolver) setDistributedAmount(distributedAmount int64) {
227 self.SetDistributedAmount(distributedAmount)
228}
229
230func (self *EmissionRewardManagerResolver) setAccumulatedTimestamp(accumulatedTimestamp int64) {
231 self.SetAccumulatedTimestamp(accumulatedTimestamp)
232}