Search Apps Documentation Source Content File Folder Download Copy Actions Download

protocol_fee_reward_state.gno

8.95 Kb · 265 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 ProtocolFeeRewardStateResolver struct {
 11	*staker.ProtocolFeeRewardState
 12}
 13
 14func NewProtocolFeeRewardStateResolver(protocolFeeRewardState *staker.ProtocolFeeRewardState) *ProtocolFeeRewardStateResolver {
 15	return &ProtocolFeeRewardStateResolver{protocolFeeRewardState}
 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 (p *ProtocolFeeRewardStateResolver) IsClaimable(currentTimestamp int64) bool {
 27	return p.GetClaimedTimestamp() < currentTimestamp
 28}
 29
 30// GetClaimableRewardAmounts calculates the claimable reward amounts for all tokens.
 31// This includes both accumulated rewards and newly earned rewards based on current state.
 32//
 33// Parameters:
 34//   - accumulatedRewardsX128PerStake: current system-wide accumulated rewards per stake for all tokens
 35//   - currentTimestamp: current timestamp
 36//
 37// Returns:
 38//   - map[string]int64: map of token path to claimable reward amount
 39//   - error: nil on success, error if claiming is not allowed
 40func (p *ProtocolFeeRewardStateResolver) GetClaimableRewardAmounts(
 41	accumulatedRewardsX128PerStake map[string]*u256.Uint,
 42	currentTimestamp int64,
 43) (map[string]int64, error) {
 44	newlyEarnedRewards, err := p.calculateClaimableRewards(accumulatedRewardsX128PerStake, currentTimestamp)
 45	if err != nil {
 46		return nil, err
 47	}
 48
 49	claimableRewards := make(map[string]int64)
 50	accumulatedRewards := p.GetAccumulatedRewards()
 51	claimedRewards := p.GetClaimedRewards()
 52
 53	for token, accumulatedReward := range accumulatedRewards {
 54		claimableRewards[token] = safeSubInt64(accumulatedReward, claimedRewards[token])
 55	}
 56
 57	for token, newlyEarnedReward := range newlyEarnedRewards {
 58		claimableRewards[token] = safeAddInt64(claimableRewards[token], newlyEarnedReward)
 59	}
 60
 61	return claimableRewards, nil
 62}
 63
 64// calculateClaimableRewards calculates newly earned rewards for all tokens since the last update.
 65// This method uses the difference between current and stored reward debt to calculate earnings.
 66//
 67// Parameters:
 68//   - accumulatedRewardsX128PerStake: current system-wide accumulated rewards per stake for all tokens
 69//   - currentTimestamp: current timestamp
 70//
 71// Returns:
 72//   - map[string]int64: map of token path to newly earned reward amount
 73func (p *ProtocolFeeRewardStateResolver) calculateClaimableRewards(
 74	accumulatedRewardsX128PerStake map[string]*u256.Uint,
 75	currentTimestamp int64,
 76) (map[string]int64, error) {
 77	// Don't calculate rewards for past timestamps
 78	if p.GetAccumulatedTimestamp() >= currentTimestamp {
 79		return make(map[string]int64), nil
 80	}
 81
 82	rewardAmounts := make(map[string]int64)
 83	stakedAmount := p.GetStakedAmount()
 84
 85	// Calculate rewards for each token type
 86	for token, accumulatedRewardX128PerStake := range accumulatedRewardsX128PerStake {
 87		// Get reward debt for this token
 88		rewardDebtX128 := p.GetRewardDebtX128ForToken(token)
 89		if rewardDebtX128 == nil {
 90			rewardDebtX128 = u256.Zero()
 91		}
 92
 93		// Calculate the difference in accumulated rewards per stake since last update
 94		// Using modular arithmetic for accumulator values - underflow is allowed and handled correctly
 95		rewardDebtDeltaX128 := u256.Zero().Sub(
 96			accumulatedRewardX128PerStake,
 97			rewardDebtX128,
 98		)
 99
100		// Multiply by staked amount to get total reward for this staker and token
101		rewardAmount := u256.MulDiv(
102			rewardDebtDeltaX128,
103			u256.NewUintFromInt64(stakedAmount),
104			q128,
105		)
106
107		rewardAmounts[token] = safeConvertToInt64(rewardAmount)
108	}
109
110	return rewardAmounts, nil
111}
112
113// addStake increases the staked amount for this address.
114// This method should be called when a user increases their stake.
115//
116// Parameters:
117//   - amount: amount of stake to add
118func (p *ProtocolFeeRewardStateResolver) addStake(amount int64) {
119	p.SetStakedAmount(safeAddInt64(p.GetStakedAmount(), amount))
120}
121
122// removeStake decreases the staked amount for this address.
123// This method should be called when a user decreases their stake.
124//
125// Parameters:
126//   - amount: amount of stake to remove
127func (p *ProtocolFeeRewardStateResolver) removeStake(amount int64) {
128	newAmount := safeSubInt64(p.GetStakedAmount(), amount)
129	if newAmount < 0 {
130		newAmount = 0
131	}
132	p.SetStakedAmount(newAmount)
133}
134
135// claimRewards processes reward claiming for all tokens 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//   - map[string]int64: map of token path to claimed reward amount
143//   - error: nil on success, error if reward debt is stale
144func (p *ProtocolFeeRewardStateResolver) claimRewards(currentTimestamp int64) (map[string]int64, error) {
145	if !p.IsClaimable(currentTimestamp) {
146		return make(map[string]int64), nil
147	}
148
149	if p.GetAccumulatedTimestamp() < currentTimestamp {
150		return nil, errors.New("must update reward debt before claiming rewards")
151	}
152
153	currentClaimedRewards := make(map[string]int64)
154	accumulatedRewards := p.GetAccumulatedRewards()
155	claimedRewards := p.GetClaimedRewards()
156
157	// Calculate and update claimed amounts for each token
158	for token, rewardAmount := range accumulatedRewards {
159		claimedAmount := claimedRewards[token]
160		currentClaimedRewards[token] = safeSubInt64(rewardAmount, claimedAmount)
161		p.SetClaimedRewardForToken(token, rewardAmount)
162	}
163
164	p.SetClaimedTimestamp(currentTimestamp)
165
166	return currentClaimedRewards, nil
167}
168
169// updateRewardDebtX128 updates the reward debt and accumulates new rewards for all tokens.
170// This method should be called before any stake changes to ensure accurate reward tracking.
171//
172// Parameters:
173//   - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake for all tokens
174//   - currentTimestamp: current timestamp
175func (p *ProtocolFeeRewardStateResolver) updateRewardDebtX128(
176	accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
177	currentTimestamp int64,
178) error {
179	// Don't update if we're looking at a past timestamp
180	if p.GetAccumulatedTimestamp() >= currentTimestamp {
181		return nil
182	}
183
184	// Calculate and accumulate new rewards for all tokens
185	rewardAmounts, err := p.calculateClaimableRewards(accumulatedProtocolFeeX128PerStake, currentTimestamp)
186	if err != nil {
187		return err
188	}
189
190	// Update reward debt for all tokens
191	p.SetRewardDebtX128(accumulatedProtocolFeeX128PerStake)
192
193	// Add newly calculated rewards to accumulated amounts
194	accumulatedRewards := p.GetAccumulatedRewards()
195	for token, rewardAmount := range rewardAmounts {
196		p.SetAccumulatedRewardForToken(token, safeAddInt64(accumulatedRewards[token], rewardAmount))
197	}
198
199	p.SetAccumulatedTimestamp(currentTimestamp)
200
201	return nil
202}
203
204// addStakeWithUpdateRewardDebtX128 adds stake and updates reward debt in one operation.
205// This ensures rewards are properly calculated before the stake change takes effect.
206//
207// Parameters:
208//   - amount: amount of stake to add
209//   - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
210//   - currentTimestamp: current timestamp
211func (p *ProtocolFeeRewardStateResolver) addStakeWithUpdateRewardDebtX128(
212	amount int64,
213	accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
214	currentTimestamp int64,
215) error {
216	err := p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
217	if err != nil {
218		return err
219	}
220
221	p.addStake(amount)
222
223	return nil
224}
225
226// removeStakeWithUpdateRewardDebtX128 removes stake and updates reward debt in one operation.
227// This ensures rewards are properly calculated before the stake change takes effect.
228//
229// Parameters:
230//   - amount: amount of stake to remove
231//   - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
232//   - currentTimestamp: current timestamp
233func (p *ProtocolFeeRewardStateResolver) removeStakeWithUpdateRewardDebtX128(
234	amount int64,
235	accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
236	currentTimestamp int64,
237) error {
238	err := p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
239	if err != nil {
240		return err
241	}
242
243	p.removeStake(amount)
244
245	return nil
246}
247
248// claimRewardsWithUpdateRewardDebtX128 claims rewards and updates reward debt in one operation.
249// This ensures all rewards are properly calculated before claiming.
250//
251// Parameters:
252//   - accumulatedProtocolFeeX128PerStake: current system-wide accumulated protocol fees per stake
253//   - currentTimestamp: current timestamp
254//
255// Returns:
256//   - map[string]int64: map of token path to claimed reward amount
257//   - error: nil on success, error if claiming fails
258func (p *ProtocolFeeRewardStateResolver) claimRewardsWithUpdateRewardDebtX128(
259	accumulatedProtocolFeeX128PerStake map[string]*u256.Uint,
260	currentTimestamp int64,
261) (map[string]int64, error) {
262	p.updateRewardDebtX128(accumulatedProtocolFeeX128PerStake, currentTimestamp)
263
264	return p.claimRewards(currentTimestamp)
265}