Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}