77 u256 "gno.land/p/gnoswap/uint256"
88)
99
10+ var errFailedToCastRewardState = "failed to cast rewardStates's element to *EmissionRewardState: %T"
11+
1012// EmissionRewardManager manages the distribution of emission rewards to stakers.
1113type EmissionRewardManager struct {
1214 // rewardStates maps address to EmissionRewardState for tracking individual staker rewards
@@ -78,7 +80,7 @@ func (e *EmissionRewardManager) calculateAccumulatedRewardX128PerStake(
7880 currentTimestamp int64,
7981) (*u256.Uint, error) {
8082 // If we're looking at a past timestamp, return current state
81- if e.accumulatedTimestamp > currentTimestamp {
83+ if currentTimestamp < e.accumulatedTimestamp {
8284 return e.accumulatedRewardX128PerStake, nil
8385 }
8486
@@ -87,14 +89,16 @@ func (e *EmissionRewardManager) calculateAccumulatedRewardX128PerStake(
8789 return e.accumulatedRewardX128PerStake, nil
8890 }
8991
90- // Calculate the newly distributed rewards since last update
92+ // Newly distributed rewards since last update
9193 distributedAmountDelta := currentDistributedAmount - e.distributedAmount
92- distributedAmountDeltaX128 := u256.NewUintFromInt64(distributedAmountDelta)
93- distributedAmountDeltaX128 = u256.Zero().Lsh(distributedAmountDeltaX128, 128)
94+ if distributedAmountDelta <= 0 {
95+ // Non-positive delta. nothing to do more.
96+ return e.accumulatedRewardX128PerStake, nil
97+ }
9498
95- // Calculate reward per stake for the new distribution
99+ // Reward per stake for the new distribution
96100 distributedAmountDeltaX128PerStake := u256.Zero().Div(
97- distributedAmountDeltaX128 ,
101+ u256.Zero().Lsh(u256.NewUintFromInt64(distributedAmountDelta), 128) ,
98102 u256.NewUintFromInt64(e.totalStakedAmount),
99103 )
100104
@@ -117,12 +121,12 @@ func (e *EmissionRewardManager) updateAccumulatedRewardX128PerStake(
117121 currentDistributedAmount int64,
118122 currentTimestamp int64,
119123) error {
120- // Don't update if we're looking at a past timestamp
121- if e.accumulatedTimestamp > currentTimestamp {
124+ // DO NOT apply out-of-order timestamps
125+ if currentTimestamp < e.accumulatedTimestamp {
122126 return nil
123127 }
124128
125- // Don't update if no tokens are staked
129+ // to avoid accumulating a large delta later.
126130 if e.totalStakedAmount == 0 {
127131 return nil
128132 }
@@ -136,7 +140,7 @@ func (e *EmissionRewardManager) updateAccumulatedRewardX128PerStake(
136140 return err
137141 }
138142
139- e.accumulatedRewardX128PerStake = accumulatedRewardX128PerStake
143+ e.accumulatedRewardX128PerStake = accumulatedRewardX128PerStake.Clone()
140144 e.distributedAmount = currentDistributedAmount
141145 e.accumulatedTimestamp = currentTimestamp
142146
@@ -147,53 +151,49 @@ func (e *EmissionRewardManager) updateAccumulatedRewardX128PerStake(
147151// This method ensures rewards are properly calculated before the stake change.
148152// Adds stake for specified address and updates reward calculations.
149153func (e *EmissionRewardManager) addStake(address string, amount int64, currentTimestamp int64) error {
150- rewardStateI , ok := e.rewardStates.Get (address)
151- if !ok {
152- rewardStateI = NewEmissionRewardState(e.accumulatedRewardX128PerStake)
154+ rewardState , ok, err := e.getRewardState (address)
155+ if err != nil {
156+ return err
153157 }
154-
155- rewardState, ok := rewardStateI.(*EmissionRewardState)
156158 if !ok {
157- return ufmt.Errorf(
158- "failed to cast rewardStates's element to *EmissionRewardState: %T",
159- rewardStateI,
160- )
159+ // if the address is unseen, initialize a snapshot to avoid nil deref
160+ rewardState = NewEmissionRewardState(e.accumulatedRewardX128PerStake.Clone())
161161 }
162162
163- err : = rewardState.addStakeWithUpdateRewardDebtX128(amount, e.accumulatedRewardX128PerStake, currentTimestamp)
163+ err = rewardState.addStakeWithUpdateRewardDebtX128(amount, e.accumulatedRewardX128PerStake, currentTimestamp)
164164 if err != nil {
165165 return err
166166 }
167167
168168 e.rewardStates.Set(address, rewardState)
169169 e.totalStakedAmount = e.totalStakedAmount + amount
170-
171170 return nil
172171}
173172
174173// removeStake removes a stake for an address and updates their reward state.
175174// This method ensures rewards are properly calculated before the stake change.
176175// Removes stake for specified address and updates reward calculations.
177176func (e *EmissionRewardManager) removeStake(address string, amount int64, currentTimestamp int64) error {
178- rewardStateI , ok := e.rewardStates.Get (address)
179- if !ok {
180- rewardStateI = NewEmissionRewardState(e.accumulatedRewardX128PerStake.Clone())
177+ rewardState , ok, err := e.getRewardState (address)
178+ if err != nil {
179+ return err
181180 }
182-
183- rewardState, ok := rewardStateI.(*EmissionRewardState)
184181 if !ok {
185- return ufmt.Errorf(
186- "failed to cast rewardStates's element to *EmissionRewardState: %T",
187- rewardStateI,
188- )
182+ // if the address is unseen, initialize a snapshot to avoid nil deref
183+ rewardState = NewEmissionRewardState(e.accumulatedRewardX128PerStake.Clone())
189184 }
190185
191- err : = rewardState.removeStakeWithUpdateRewardDebtX128(amount, e.accumulatedRewardX128PerStake, currentTimestamp)
186+ err = rewardState.removeStakeWithUpdateRewardDebtX128(amount, e.accumulatedRewardX128PerStake, currentTimestamp)
192187 if err != nil {
193188 return err
194189 }
195190
196- e.totalStakedAmount = e.totalStakedAmount - amount
191+ // persist updated state
192+ e.rewardStates.Set(address, rewardState)
193+ e.totalStakedAmount -= amount
194+ if e.totalStakedAmount < 0 {
195+ e.totalStakedAmount = 0 // defensive clamp
196+ }
197197
198198 return nil
199199}
@@ -202,17 +202,9 @@ func (e *EmissionRewardManager) removeStake(address string, amount int64, curren
202202// This method calculates and returns the amount of rewards claimed.
203203// Claims available rewards for specified address.
204204func (e *EmissionRewardManager) claimRewards(address string, currentTimestamp int64) (claimedRewardAmount int64, err error) {
205- rewardStateI, ok := e.rewardStates.Get(address)
206- if !ok {
207- return 0, nil
208- }
209-
210- rewardState, ok := rewardStateI.(*EmissionRewardState)
211- if !ok {
212- return 0, ufmt.Errorf(
213- "failed to cast rewardStates's element to *EmissionRewardState: %T",
214- rewardStateI,
215- )
205+ rewardState, ok, err := e.getRewardState(address)
206+ if err != nil || !ok {
207+ return 0, err
216208 }
217209
218210 claimedRewardAmount, cErr := rewardState.claimRewardsWithUpdateRewardDebtX128(e.accumulatedRewardX128PerStake, currentTimestamp)
@@ -221,7 +213,6 @@ func (e *EmissionRewardManager) claimRewards(address string, currentTimestamp in
221213 }
222214
223215 e.rewardStates.Set(address, rewardState)
224-
225216 return claimedRewardAmount, nil
226217}
227218
@@ -237,3 +228,15 @@ func NewEmissionRewardManager() *EmissionRewardManager {
237228 rewardStates: avl.NewTree(),
238229 }
239230}
231+
232+ func (e *EmissionRewardManager) getRewardState(addr string) (*EmissionRewardState, bool, error) {
233+ ri, ok := e.rewardStates.Get(addr)
234+ if !ok {
235+ return nil, false, nil
236+ }
237+ rs, castOk := ri.(*EmissionRewardState)
238+ if !castOk {
239+ return nil, false, ufmt.Errorf(errFailedToCastRewardState, ri)
240+ }
241+ return rs, true, nil
242+ }
0 commit comments