Search Apps Documentation Source Content File Folder Download Copy Actions Download

emission_reward_state.gno

7.72 Kb · 229 lines
  1package v1
  2
  3import (
  4	u256 "gno.land/p/gnoswap/uint256"
  5	"gno.land/r/gnoswap/gov/staker"
  6)
  7
  8type EmissionRewardStateResolver struct {
  9	*staker.EmissionRewardState
 10}
 11
 12func NewEmissionRewardStateResolver(emissionRewardState *staker.EmissionRewardState) *EmissionRewardStateResolver {
 13	return &EmissionRewardStateResolver{emissionRewardState}
 14}
 15
 16// IsClaimable checks if rewards can be claimed at the given timestamp.
 17// Rewards are claimable if the current timestamp is greater than the last claimed timestamp.
 18//
 19// Parameters:
 20//   - currentTimestamp: current timestamp to check against
 21//
 22// Returns:
 23//   - bool: true if rewards can be claimed, false otherwise
 24func (self *EmissionRewardStateResolver) IsClaimable(currentTimestamp int64) bool {
 25	return self.GetClaimedTimestamp() < currentTimestamp
 26}
 27
 28// GetClaimableRewardAmount calculates the total amount of rewards that can be claimed.
 29// This includes both accumulated rewards and newly earned rewards based on current state.
 30//
 31// Parameters:
 32//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
 33//   - currentTimestamp: current timestamp
 34//
 35// Returns:
 36//   - int64: total claimable reward amount
 37func (self *EmissionRewardStateResolver) GetClaimableRewardAmount(
 38	accumulatedRewardX128PerStake *u256.Uint,
 39	currentTimestamp int64,
 40) (int64, error) {
 41	rewardAmount, err := self.calculateClaimableRewards(accumulatedRewardX128PerStake, currentTimestamp)
 42	if err != nil {
 43		return 0, err
 44	}
 45
 46	accumulatedUnclaimedReward := safeSubInt64(
 47		self.GetAccumulatedRewardAmount(),
 48		self.GetClaimedRewardAmount(),
 49	)
 50
 51	return safeAddInt64(accumulatedUnclaimedReward, rewardAmount), nil
 52}
 53
 54// calculateClaimableRewards calculates newly earned rewards since the last update.
 55// Uses the difference between current and stored reward debt to calculate earnings.
 56//
 57// Parameters:
 58//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
 59//   - currentTimestamp: current timestamp
 60//
 61// Returns:
 62//   - int64: newly earned reward amount since last update
 63//   - error: nil on success, error if calculation fails
 64func (self *EmissionRewardStateResolver) calculateClaimableRewards(
 65	accumulatedRewardX128PerStake *u256.Uint,
 66	currentTimestamp int64,
 67) (int64, error) {
 68	stakedAmount := self.GetStakedAmount()
 69
 70	// Don't calculate rewards for past timestamps or when nothing is staked
 71	if currentTimestamp < self.GetAccumulatedTimestamp() || stakedAmount == 0 {
 72		return 0, nil
 73	}
 74
 75	// Calculate the difference in accumulated rewards per stake since last update
 76	// Using modular arithmetic for accumulator values - underflow is allowed and handled correctly
 77	rewardDebtDeltaX128 := u256.Zero().Sub(
 78		accumulatedRewardX128PerStake,
 79		self.GetRewardDebtX128(),
 80	)
 81
 82	// Calculate reward amount by multiplying reward debt delta by staked amount and dividing by Q128
 83	// rewardAmount = (rewardDebtDeltaX128 * stakedAmount) / Q128
 84	rewardAmount := u256.MulDiv(
 85		rewardDebtDeltaX128,
 86		u256.NewUintFromInt64(stakedAmount),
 87		q128,
 88	)
 89	return safeConvertToInt64(rewardAmount), nil
 90}
 91
 92// addStake increases the staked amount for this address.
 93// This method should be called when a user increases their stake.
 94//
 95// Parameters:
 96//   - amount: amount of stake to add
 97func (self *EmissionRewardStateResolver) addStake(amount int64) {
 98	self.adjustStake(amount)
 99}
100
101// removeStake decreases the staked amount for this address.
102// This method should be called when a user decreases their stake.
103//
104// Parameters:
105//   - amount: amount of stake to remove
106func (self *EmissionRewardStateResolver) removeStake(amount int64) {
107	self.adjustStake(-amount)
108}
109
110// adjustStake is a small internal helper to centralize bound checks and math.
111func (self *EmissionRewardStateResolver) adjustStake(delta int64) {
112	if delta == 0 {
113		return
114	}
115	// clamp at zero on underflow
116	newAmt := safeAddInt64(self.GetStakedAmount(), delta)
117	if newAmt < 0 {
118		newAmt = 0
119	}
120	self.SetStakedAmount(newAmt)
121}
122
123// claimRewards processes reward claiming and updates the claim state.
124// This method validates claimability and transfers accumulated rewards to claimed status.
125//
126// Parameters:
127//   - currentTimestamp: current timestamp
128//
129// Returns:
130//   - int64: amount of rewards claimed (0 if already claimed at this timestamp)
131//   - error: always nil in the current implementation
132func (self *EmissionRewardStateResolver) claimRewards(currentTimestamp int64) (int64, error) {
133	if !self.IsClaimable(currentTimestamp) {
134		return 0, nil
135	}
136
137	accumulatedRewardAmount := self.GetAccumulatedRewardAmount()
138	previousClaimedAmount := self.GetClaimedRewardAmount()
139	claimableAmount := safeSubInt64(accumulatedRewardAmount, previousClaimedAmount)
140
141	self.SetClaimedRewardAmount(accumulatedRewardAmount)
142	self.SetClaimedTimestamp(currentTimestamp)
143
144	return claimableAmount, nil
145}
146
147// updateRewardDebtX128 updates the reward debt and accumulates new rewards.
148// This method should be called before any stake changes to ensure accurate reward tracking.
149//
150// Parameters:
151//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
152//   - currentTimestamp: current timestamp
153func (self *EmissionRewardStateResolver) updateRewardDebtX128(
154	accumulatedRewardX128PerStake *u256.Uint,
155	currentTimestamp int64,
156) error {
157	rewardAmount, err := self.calculateClaimableRewards(accumulatedRewardX128PerStake, currentTimestamp)
158	if err != nil {
159		return err
160	}
161
162	// Accumulate newly earned rewards
163	if rewardAmount != 0 {
164		self.SetAccumulatedRewardAmount(safeAddInt64(self.GetAccumulatedRewardAmount(), rewardAmount))
165	}
166
167	// Deep copy to avoid aliasing with external state
168	self.SetRewardDebtX128(accumulatedRewardX128PerStake.Clone())
169	self.SetAccumulatedTimestamp(currentTimestamp)
170	return nil
171}
172
173// addStakeWithUpdateRewardDebtX128 adds stake and updates reward debt in one operation.
174// This ensures rewards are properly calculated before the stake change takes effect.
175//
176// Parameters:
177//   - amount: amount of stake to add
178//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
179//   - currentTimestamp: current timestamp
180func (self *EmissionRewardStateResolver) addStakeWithUpdateRewardDebtX128(
181	amount int64,
182	accumulatedRewardX128PerStake *u256.Uint,
183	currentTimestamp int64,
184) error {
185	if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
186		return err
187	}
188	self.addStake(amount)
189	return nil
190}
191
192// removeStakeWithUpdateRewardDebtX128 removes stake and updates reward debt in one operation.
193// This ensures rewards are properly calculated before the stake change takes effect.
194//
195// Parameters:
196//   - amount: amount of stake to remove
197//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
198//   - currentTimestamp: current timestamp
199func (self *EmissionRewardStateResolver) removeStakeWithUpdateRewardDebtX128(
200	amount int64,
201	accumulatedRewardX128PerStake *u256.Uint,
202	currentTimestamp int64,
203) error {
204	if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
205		return err
206	}
207	self.removeStake(amount)
208	return nil
209}
210
211// claimRewardsWithUpdateRewardDebtX128 claims rewards and updates reward debt in one operation.
212// This ensures all rewards are properly calculated before claiming.
213//
214// Parameters:
215//   - accumulatedRewardX128PerStake: current system-wide accumulated reward per stake
216//   - currentTimestamp: current timestamp
217//
218// Returns:
219//   - int64: amount of rewards claimed
220//   - error: nil on success, error if claiming fails
221func (self *EmissionRewardStateResolver) claimRewardsWithUpdateRewardDebtX128(
222	accumulatedRewardX128PerStake *u256.Uint,
223	currentTimestamp int64,
224) (int64, error) {
225	if err := self.updateRewardDebtX128(accumulatedRewardX128PerStake, currentTimestamp); err != nil {
226		return 0, err
227	}
228	return self.claimRewards(currentTimestamp)
229}