emission_reward_manager.gno
6.92 Kb · 213 lines
1package staker
2
3import (
4 "errors"
5 "math"
6
7 gnsmath "gno.land/p/gnoswap/gnsmath"
8 u256 "gno.land/p/gnoswap/uint256"
9 "gno.land/r/gnoswap/gov/staker"
10)
11
12type EmissionRewardManagerResolver struct {
13 *staker.EmissionRewardManager
14}
15
16func NewEmissionRewardManagerResolver(emissionRewardManager *staker.EmissionRewardManager) *EmissionRewardManagerResolver {
17 return &EmissionRewardManagerResolver{emissionRewardManager}
18}
19
20// GetClaimableRewardAmount calculates the claimable reward amount for a specific address.
21func (self *EmissionRewardManagerResolver) GetClaimableRewardAmount(
22 currentDistributedAmount int64,
23 address string,
24 currentTimestamp int64,
25) (int64, error) {
26 rewardState, ok, err := self.GetRewardState(address)
27 if !ok {
28 return 0, nil
29 }
30
31 accumulatedRewardX128PerStake, err := self.calculateAccumulatedRewardX128PerStake(
32 currentDistributedAmount,
33 currentTimestamp,
34 )
35 if err != nil {
36 return 0, err
37 }
38
39 resolvedState := NewEmissionRewardStateResolver(rewardState)
40 return resolvedState.GetClaimableRewardAmount(accumulatedRewardX128PerStake, currentTimestamp)
41}
42
43// calculateAccumulatedRewardX128PerStake calculates the updated accumulated reward per stake.
44func (self *EmissionRewardManagerResolver) calculateAccumulatedRewardX128PerStake(
45 currentDistributedAmount int64,
46 currentTimestamp int64,
47) (*u256.Uint, error) {
48 // If we're looking at a past timestamp, return current state
49 if currentTimestamp < self.GetAccumulatedTimestamp() {
50 return self.GetAccumulatedRewardX128PerStake(), nil
51 }
52
53 // If no tokens are staked, no rewards to distribute
54 totalStakedAmount := self.GetTotalStakedAmount()
55 if totalStakedAmount == 0 {
56 return self.GetAccumulatedRewardX128PerStake(), nil
57 }
58
59 // Newly distributed rewards since last update
60 distributedAmountDelta := gnsmath.SafeSubInt64(currentDistributedAmount, self.GetDistributedAmount())
61 if distributedAmountDelta <= 0 {
62 // Non-positive delta. nothing to do more.
63 return self.GetAccumulatedRewardX128PerStake(), nil
64 }
65
66 // Reward per stake for the new distribution
67 distributedAmountDeltaX128PerStake := u256.Zero().Div(
68 u256.Zero().Lsh(u256.NewUintFromInt64(distributedAmountDelta), 128),
69 u256.NewUintFromInt64(totalStakedAmount),
70 )
71
72 // Add to accumulated reward per stake
73 accumulatedReward := u256.Zero().Add(self.GetAccumulatedRewardX128PerStake(), distributedAmountDeltaX128PerStake)
74 return accumulatedReward, nil
75}
76
77// updateAccumulatedRewardX128PerStake updates the internal accumulated reward state.
78// This method should be called before any stake changes to ensure accurate reward calculations.
79// Updates accumulated reward per stake with current distribution data.
80func (self *EmissionRewardManagerResolver) updateAccumulatedRewardX128PerStake(
81 currentDistributedAmount int64,
82 currentTimestamp int64,
83) error {
84 // DO NOT apply out-of-order timestamps
85 if currentTimestamp < self.GetAccumulatedTimestamp() {
86 return nil
87 }
88
89 // to avoid accumulating a large delta later.
90 if self.GetTotalStakedAmount() == 0 {
91 return nil
92 }
93
94 // Update accumulated reward state
95 accumulatedRewardX128PerStake, err := self.calculateAccumulatedRewardX128PerStake(
96 currentDistributedAmount,
97 currentTimestamp,
98 )
99 if err != nil {
100 return err
101 }
102
103 self.setAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake.Clone())
104 self.setDistributedAmount(currentDistributedAmount)
105 self.setAccumulatedTimestamp(currentTimestamp)
106
107 return nil
108}
109
110// addStake adds a stake for an address and updates their reward state.
111// This method ensures rewards are properly calculated before the stake change.
112// Adds stake for specified address and updates reward calculations.
113func (self *EmissionRewardManagerResolver) addStake(address string, amount int64, currentTimestamp int64) error {
114 if amount < 0 {
115 return errors.New("amount must be non-negative")
116 }
117
118 accumulatedReward := self.GetAccumulatedRewardX128PerStake()
119
120 rewardState, ok, err := self.GetRewardState(address)
121 if err != nil {
122 return err
123 }
124 if !ok {
125 rewardState = staker.NewEmissionRewardState(accumulatedReward)
126 }
127
128 resolvedState := NewEmissionRewardStateResolver(rewardState)
129 err = resolvedState.addStakeWithUpdateRewardDebtX128(amount, accumulatedReward, currentTimestamp)
130 if err != nil {
131 return err
132 }
133
134 self.setRewardStates(address, rewardState)
135
136 currentTotal := self.GetTotalStakedAmount()
137 if amount > 0 && currentTotal > math.MaxInt64-amount {
138 return errors.New("total staked amount would overflow")
139 }
140 self.SetTotalStakedAmount(gnsmath.SafeAddInt64(currentTotal, amount))
141 return nil
142}
143
144// removeStake removes a stake for an address and updates their reward state.
145// This method ensures rewards are properly calculated before the stake change.
146// Removes stake for specified address and updates reward calculations.
147func (self *EmissionRewardManagerResolver) removeStake(address string, amount int64, currentTimestamp int64) error {
148 if amount < 0 {
149 return errors.New("amount must be non-negative")
150 }
151
152 accumulatedReward := self.GetAccumulatedRewardX128PerStake()
153
154 rewardState, ok, err := self.GetRewardState(address)
155 if err != nil {
156 return err
157 }
158 if !ok {
159 rewardState = staker.NewEmissionRewardState(accumulatedReward)
160 }
161
162 resolvedState := NewEmissionRewardStateResolver(rewardState)
163 err = resolvedState.removeStakeWithUpdateRewardDebtX128(amount, accumulatedReward, currentTimestamp)
164 if err != nil {
165 return err
166 }
167
168 // persist updated state
169 self.setRewardStates(address, rewardState)
170
171 updatedTotalStakedAmount := gnsmath.SafeSubInt64(self.GetTotalStakedAmount(), amount)
172 if updatedTotalStakedAmount < 0 {
173 updatedTotalStakedAmount = 0
174 }
175 self.SetTotalStakedAmount(updatedTotalStakedAmount)
176
177 return nil
178}
179
180// claimRewards processes reward claiming for an address.
181// This method calculates and returns the amount of rewards claimed.
182// Claims available rewards for specified address.
183func (self *EmissionRewardManagerResolver) claimRewards(address string, currentTimestamp int64) (claimedRewardAmount int64, err error) {
184 rewardState, ok, err := self.GetRewardState(address)
185 if err != nil || !ok {
186 return 0, err
187 }
188
189 resolvedState := NewEmissionRewardStateResolver(rewardState)
190 claimedRewardAmount, cErr := resolvedState.claimRewardsWithUpdateRewardDebtX128(self.GetAccumulatedRewardX128PerStake(), currentTimestamp)
191 if cErr != nil {
192 return 0, cErr
193 }
194
195 self.setRewardStates(address, rewardState)
196 return claimedRewardAmount, nil
197}
198
199func (self *EmissionRewardManagerResolver) setRewardStates(address string, rewardState *staker.EmissionRewardState) {
200 self.SetRewardState(address, rewardState)
201}
202
203func (self *EmissionRewardManagerResolver) setAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake *u256.Uint) {
204 self.SetAccumulatedRewardX128PerStake(accumulatedRewardX128PerStake)
205}
206
207func (self *EmissionRewardManagerResolver) setDistributedAmount(distributedAmount int64) {
208 self.SetDistributedAmount(distributedAmount)
209}
210
211func (self *EmissionRewardManagerResolver) setAccumulatedTimestamp(accumulatedTimestamp int64) {
212 self.SetAccumulatedTimestamp(accumulatedTimestamp)
213}