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_state.gno

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