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

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}