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

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