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

protocol_fee_reward_manager.gno

12.42 Kb · 358 lines
  1package staker
  2
  3import (
  4	"errors"
  5	"math"
  6
  7	gnsmath "gno.land/p/gnoswap/gnsmath"
  8	u256 "gno.land/p/gnoswap/uint256"
  9	"gno.land/r/gnoswap/gov/staker"
 10)
 11
 12type ProtocolFeeRewardManagerResolver struct {
 13	*staker.ProtocolFeeRewardManager
 14}
 15
 16func NewProtocolFeeRewardManagerResolver(manager *staker.ProtocolFeeRewardManager) *ProtocolFeeRewardManagerResolver {
 17	return &ProtocolFeeRewardManagerResolver{manager}
 18}
 19
 20// GetClaimableRewardAmounts calculates the claimable reward amounts for all tokens for a specific address.
 21// This method computes rewards based on current protocol fee distribution state and staking history.
 22//
 23// Parameters:
 24//   - protocolFeeAmounts: current protocol fee amounts for all tokens
 25//   - address: staker's address to calculate rewards for
 26//   - currentTimestamp: current timestamp
 27//
 28// Returns:
 29//   - map[string]int64: map of token path to claimable reward amount
 30func (self *ProtocolFeeRewardManagerResolver) GetClaimableRewardAmounts(
 31	protocolFeeAmounts map[string]int64,
 32	address string,
 33	currentTimestamp int64,
 34) (map[string]int64, error) {
 35	rewardState, ok, err := self.GetRewardState(address)
 36	if err != nil {
 37		return nil, err
 38	}
 39	if !ok {
 40		return make(map[string]int64), nil
 41	}
 42
 43	accumulatedRewardX128PerStake, _, err := self.calculateAccumulatedRewardX128PerStake(
 44		protocolFeeAmounts,
 45		currentTimestamp,
 46	)
 47	if err != nil {
 48		return nil, err
 49	}
 50
 51	resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
 52
 53	return resolvedState.GetClaimableRewardAmounts(accumulatedRewardX128PerStake, currentTimestamp)
 54}
 55
 56// calculateAccumulatedRewardX128PerStake calculates the updated accumulated reward per stake for all tokens.
 57// This method computes new accumulated reward rates based on newly distributed protocol fees.
 58//
 59// Parameters:
 60//   - protocolFeeAmounts: current protocol fee amounts for all tokens
 61//   - currentTimestamp: current timestamp
 62//
 63// Returns:
 64//   - map[string]*u256.Uint: updated accumulated reward per stake for each token
 65//   - map[string]int64: updated protocol fee amounts for each token
 66func (self *ProtocolFeeRewardManagerResolver) calculateAccumulatedRewardX128PerStake(
 67	protocolFeeAmounts map[string]int64,
 68	currentTimestamp int64,
 69) (map[string]*u256.Uint, map[string]int64, error) {
 70	// If we're looking at a past timestamp, return current state
 71	if self.GetAccumulatedTimestamp() > currentTimestamp {
 72		return self.GetAllAccumulatedProtocolFeeX128PerStake(), self.GetProtocolFeeAmounts(), nil
 73	}
 74
 75	accumulatedProtocolFeesX128PerStake := make(map[string]*u256.Uint)
 76	changedProtocolFeeAmounts := make(map[string]int64)
 77
 78	// Process each token's protocol fees
 79	for token, protocolFeeAmount := range protocolFeeAmounts {
 80		previousProtocolFeeAmount := self.GetProtocolFeeAmount(token)
 81
 82		protocolFeeDelta := gnsmath.SafeSubInt64(protocolFeeAmount, previousProtocolFeeAmount)
 83
 84		// If no new fees for this token, keep existing rate
 85		if protocolFeeDelta <= 0 {
 86			accumulatedProtocolFeesX128PerStake[token] = self.GetAccumulatedProtocolFeeX128PerStake(token)
 87			if accumulatedProtocolFeesX128PerStake[token] == nil {
 88				accumulatedProtocolFeesX128PerStake[token] = u256.NewUint(0)
 89			}
 90			changedProtocolFeeAmounts[token] = protocolFeeAmount
 91			continue
 92		}
 93
 94		// Scale the fee delta by 2^128 for precision
 95		protocolFeeDeltaX128 := u256.NewUintFromInt64(protocolFeeDelta)
 96		protocolFeeDeltaX128 = u256.Zero().Lsh(protocolFeeDeltaX128, 128)
 97
 98		protocolFeeDeltaX128PerStake := u256.Zero()
 99
100		// Calculate fee per stake if there are staked tokens
101		if self.GetTotalStakedAmount() > 0 {
102			feePerStake := u256.Zero().Div(protocolFeeDeltaX128, u256.NewUintFromInt64(self.GetTotalStakedAmount()))
103			protocolFeeDeltaX128PerStake = feePerStake
104		}
105
106		// Get current accumulated fee per stake for this token
107		accumulatedProtocolFeeX128PerStake := u256.Zero()
108		existingAccumulatedFee := self.GetAccumulatedProtocolFeeX128PerStake(token)
109		if existingAccumulatedFee != nil {
110			accumulatedProtocolFeeX128PerStake = existingAccumulatedFee
111		}
112
113		// Add the new fee per stake to the accumulated amount
114		accumulatedProtocolFeeX128PerStake = u256.Zero().Add(accumulatedProtocolFeeX128PerStake, protocolFeeDeltaX128PerStake)
115		accumulatedProtocolFeesX128PerStake[token] = accumulatedProtocolFeeX128PerStake.Clone()
116
117		changedProtocolFeeAmounts[token] = protocolFeeAmount
118	}
119
120	return accumulatedProtocolFeesX128PerStake, changedProtocolFeeAmounts, nil
121}
122
123// calculateAccumulatedRewardX128PerStakeForToken calculates accumulated reward per stake for exactly one token.
124func (self *ProtocolFeeRewardManagerResolver) calculateAccumulatedRewardX128PerStakeForToken(
125	tokenPath string,
126	protocolFeeAmount int64,
127) (*u256.Uint, int64, error) {
128	previousProtocolFeeAmount := self.GetProtocolFeeAmount(tokenPath)
129	protocolFeeDelta := gnsmath.SafeSubInt64(protocolFeeAmount, previousProtocolFeeAmount)
130
131	existingAccumulatedFee := self.GetAccumulatedProtocolFeeX128PerStake(tokenPath)
132	if existingAccumulatedFee == nil {
133		existingAccumulatedFee = u256.Zero()
134	}
135
136	if protocolFeeDelta <= 0 {
137		return existingAccumulatedFee.Clone(), protocolFeeAmount, nil
138	}
139
140	protocolFeeDeltaX128 := u256.NewUintFromInt64(protocolFeeDelta)
141	protocolFeeDeltaX128 = u256.Zero().Lsh(protocolFeeDeltaX128, 128)
142
143	protocolFeeDeltaX128PerStake := u256.Zero()
144	if self.GetTotalStakedAmount() > 0 {
145		protocolFeeDeltaX128PerStake = u256.Zero().Div(protocolFeeDeltaX128, u256.NewUintFromInt64(self.GetTotalStakedAmount()))
146	}
147
148	accumulatedProtocolFeeX128PerStake := u256.Zero().Add(existingAccumulatedFee, protocolFeeDeltaX128PerStake)
149
150	return accumulatedProtocolFeeX128PerStake.Clone(), protocolFeeAmount, nil
151}
152
153// updateAccumulatedProtocolFeeX128PerStake updates the internal accumulated protocol fee state.
154// This method should be called before any stake changes to ensure accurate reward calculations.
155//
156// Parameters:
157//   - protocolFeeAmounts: current protocol fee amounts for all tokens
158//   - currentTimestamp: current timestamp
159func (self *ProtocolFeeRewardManagerResolver) updateAccumulatedProtocolFeeX128PerStake(
160	protocolFeeAmounts map[string]int64,
161	currentTimestamp int64,
162) error {
163	// Don't update if we're looking at a past timestamp
164	if self.GetAccumulatedTimestamp() > currentTimestamp {
165		return nil
166	}
167
168	accumulatedProtocolFeeX128PerStake, changedProtocolFeeAmounts, err := self.calculateAccumulatedRewardX128PerStake(
169		protocolFeeAmounts,
170		currentTimestamp,
171	)
172	if err != nil {
173		return err
174	}
175
176	// Persist only the tokens whose value actually changed instead of replacing the whole map.
177	// calculateAccumulatedRewardX128PerStake returns the previous (identical) accumulator and amount for
178	// tokens that received no new fees this round, so the full-map setters would deep-copy and re-persist
179	// every registered token on every add/remove/claim, growing storage and gas linearly in the number of
180	// registered tokens regardless of how many actually earned fees (gas report issue 1 / §1.2).
181	//
182	// Registered protocol-fee tokens are monotonic (never removed) and distributedAmounts always covers the
183	// full registered set, so the stored token set never shrinks; skipping unchanged entries therefore yields
184	// the same persisted state as the previous full-replace, only without the redundant re-writes.
185	for token, newAcc := range accumulatedProtocolFeeX128PerStake {
186		existing := self.GetAccumulatedProtocolFeeX128PerStake(token)
187		if existing == nil || !existing.Eq(newAcc) {
188			self.SetAccumulatedProtocolFeeX128PerStakeForToken(token, newAcc)
189		}
190	}
191	for token, amount := range changedProtocolFeeAmounts {
192		if self.GetProtocolFeeAmount(token) != amount {
193			self.SetProtocolFeeAmountForToken(token, amount)
194		}
195	}
196
197	self.SetAccumulatedTimestamp(currentTimestamp)
198
199	return nil
200}
201
202// updateAccumulatedProtocolFeeX128PerStakeForToken updates manager accumulators for exactly one token.
203func (self *ProtocolFeeRewardManagerResolver) updateAccumulatedProtocolFeeX128PerStakeForToken(
204	tokenPath string,
205	protocolFeeAmount int64,
206	currentTimestamp int64,
207) error {
208	accumulatedProtocolFeeX128PerStake, changedProtocolFeeAmount, err := self.calculateAccumulatedRewardX128PerStakeForToken(
209		tokenPath,
210		protocolFeeAmount,
211	)
212	if err != nil {
213		return err
214	}
215
216	self.SetAccumulatedProtocolFeeX128PerStakeForToken(tokenPath, accumulatedProtocolFeeX128PerStake)
217	self.SetProtocolFeeAmountForToken(tokenPath, changedProtocolFeeAmount)
218
219	return nil
220}
221
222// addStake adds a stake for an address and updates their protocol fee reward state.
223// This method ensures rewards are properly calculated before the stake change.
224//
225// Parameters:
226//   - address: staker's address
227//   - amount: amount of stake to add
228//   - currentTimestamp: current timestamp
229func (self *ProtocolFeeRewardManagerResolver) addStake(address string, amount int64, currentTimestamp int64) error {
230	if amount <= 0 {
231		return errors.New("amount must be positive")
232	}
233
234	rewardState, ok, err := self.GetRewardState(address)
235	if err != nil {
236		return err
237	}
238	if !ok {
239		rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
240	}
241
242	resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
243
244	currentTotal := self.GetTotalStakedAmount()
245	if currentTotal > math.MaxInt64-amount {
246		return errors.New("total staked amount would overflow")
247	}
248	updatedTotalStakedAmount := gnsmath.SafeAddInt64(currentTotal, amount)
249
250	err = resolvedState.addStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
251	if err != nil {
252		return err
253	}
254
255	self.setRewardState(address, rewardState)
256	self.SetTotalStakedAmount(updatedTotalStakedAmount)
257
258	return nil
259}
260
261// removeStake removes a stake for an address and updates their protocol fee reward state.
262// This method ensures rewards are properly calculated before the stake change.
263//
264// Parameters:
265//   - address: staker's address
266//   - amount: amount of stake to remove
267//   - currentTimestamp: current timestamp
268func (self *ProtocolFeeRewardManagerResolver) removeStake(address string, amount int64, currentTimestamp int64) error {
269	if amount < 0 {
270		return errors.New("amount must be non-negative")
271	}
272
273	rewardState, ok, err := self.GetRewardState(address)
274	if err != nil {
275		return err
276	}
277	if !ok {
278		rewardState = staker.NewProtocolFeeRewardState(self.GetAllAccumulatedProtocolFeeX128PerStake())
279	}
280
281	resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
282	err = resolvedState.removeStakeWithUpdateRewardDebtX128(amount, self.GetAllAccumulatedProtocolFeeX128PerStake(), currentTimestamp)
283	if err != nil {
284		return err
285	}
286
287	self.setRewardState(address, rewardState)
288
289	updatedTotalStakedAmount := gnsmath.SafeSubInt64(self.GetTotalStakedAmount(), amount)
290	if updatedTotalStakedAmount < 0 {
291		updatedTotalStakedAmount = 0
292	}
293	self.SetTotalStakedAmount(updatedTotalStakedAmount)
294
295	return nil
296}
297
298// claimRewards processes protocol fee reward claiming for an address.
299// This method calculates and returns the amounts of rewards claimed for each token.
300//
301// Parameters:
302//   - address: staker's address claiming rewards
303//   - currentTimestamp: current timestamp
304//
305// Returns:
306//   - map[string]int64: map of token path to claimed reward amount
307//   - error: nil on success, error if claiming fails
308func (self *ProtocolFeeRewardManagerResolver) claimRewards(address string, currentTimestamp int64) (map[string]int64, error) {
309	rewardState, ok, err := self.GetRewardState(address)
310	if err != nil {
311		return nil, err
312	}
313	if !ok {
314		return make(map[string]int64), nil
315	}
316
317	resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
318	claimedRewards, err := resolvedState.claimRewardsWithUpdateRewardDebtX128(
319		self.GetAllAccumulatedProtocolFeeX128PerStake(),
320		currentTimestamp,
321	)
322	if err != nil {
323		return nil, err
324	}
325
326	self.setRewardState(address, rewardState)
327
328	return claimedRewards, nil
329}
330
331// claimRewardForToken processes protocol fee reward claiming for exactly one token.
332func (self *ProtocolFeeRewardManagerResolver) claimRewardForToken(address string, tokenPath string, currentTimestamp int64) (int64, error) {
333	rewardState, ok, err := self.GetRewardState(address)
334	if err != nil {
335		return 0, err
336	}
337	if !ok {
338		return 0, nil
339	}
340
341	resolvedState := NewProtocolFeeRewardStateResolver(rewardState)
342	claimedReward, err := resolvedState.claimRewardForTokenWithUpdateRewardDebtX128(
343		tokenPath,
344		self.GetAccumulatedProtocolFeeX128PerStake(tokenPath),
345		currentTimestamp,
346	)
347	if err != nil {
348		return 0, err
349	}
350
351	self.setRewardState(address, rewardState)
352
353	return claimedReward, nil
354}
355
356func (self *ProtocolFeeRewardManagerResolver) setRewardState(address string, rewardState *staker.ProtocolFeeRewardState) {
357	self.SetRewardState(address, rewardState)
358}