Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}