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