Search Apps Documentation Source Content File Folder Download Copy Actions Download

emission_reward_state.gno

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