Search Apps Documentation Source Content File Folder Download Copy Actions Download

calculate_pool_position_reward.gno

6.99 Kb ยท 215 lines
  1package v1
  2
  3import (
  4	"gno.land/p/nt/avl"
  5	sr "gno.land/r/gnoswap/staker"
  6)
  7
  8// Reward is a struct for storing reward for a position.
  9// Internal reward is the GNS reward, external reward is the reward for other incentives.
 10// Penalties are the amount that is deducted from the reward due to the position's warmup.
 11type Reward struct {
 12	Internal        int64
 13	InternalPenalty int64
 14	External        map[string]int64 // Incentive ID -> TokenAmount
 15	ExternalPenalty map[string]int64 // Incentive ID -> TokenAmount
 16}
 17
 18// calculate total position rewards and penalties
 19func (s *stakerV1) calcPositionReward(currentHeight, currentTimestamp int64, positionId uint64) Reward {
 20	rewards := s.calculatePositionReward(&CalcPositionRewardParam{
 21		CurrentHeight: currentHeight,
 22		CurrentTime:   currentTimestamp,
 23		Deposits:      s.getDeposits(),
 24		Pools:         s.getPools(),
 25		PoolTier:      s.getPoolTier(),
 26		PositionId:    positionId,
 27	})
 28
 29	internal := int64(0)
 30	internalPenalty := int64(0)
 31
 32	rewardLen := len(rewards)
 33	externalReward := make(map[string]int64, rewardLen)
 34	externalPenalty := make(map[string]int64, rewardLen)
 35
 36	for _, reward := range rewards {
 37		internal = safeAddInt64(internal, reward.Internal)
 38		internalPenalty = safeAddInt64(internalPenalty, reward.InternalPenalty)
 39
 40		for incentive, amount := range reward.External {
 41			externalReward[incentive] = safeAddInt64(externalReward[incentive], amount)
 42		}
 43
 44		for incentive, penalty := range reward.ExternalPenalty {
 45			externalPenalty[incentive] = safeAddInt64(externalPenalty[incentive], penalty)
 46		}
 47	}
 48
 49	return Reward{
 50		Internal:        internal,
 51		InternalPenalty: internalPenalty,
 52		External:        externalReward,
 53		ExternalPenalty: externalPenalty,
 54	}
 55}
 56
 57// CalcPositionRewardParam is a struct for calculating position reward
 58type CalcPositionRewardParam struct {
 59	// Environmental variables
 60	CurrentHeight int64
 61	CurrentTime   int64
 62	Deposits      *Deposits
 63	Pools         *Pools
 64	PoolTier      *PoolTier
 65
 66	// Position variables
 67	PositionId uint64
 68}
 69
 70func (s *stakerV1) calculatePositionReward(param *CalcPositionRewardParam) []Reward {
 71	// cache per-pool rewards in the internal incentive(tiers)
 72	param.PoolTier.cacheReward(param.CurrentHeight, param.CurrentTime, param.Pools)
 73	s.updatePoolTier(param.PoolTier)
 74
 75	deposit := param.Deposits.get(param.PositionId)
 76	depositResolver := NewDepositResolver(deposit)
 77	poolPath := deposit.TargetPoolPath()
 78
 79	pool, ok := param.Pools.Get(poolPath)
 80	if !ok {
 81		pool = sr.NewPool(poolPath, param.CurrentTime)
 82		param.Pools.set(poolPath, pool)
 83	}
 84	poolResolver := NewPoolResolver(pool)
 85
 86	lastCollectTime := depositResolver.InternalRewardLastCollectTime()
 87
 88	// Initializes reward/penalty arrays for rewards and penalties for each warmup
 89	rewardState := poolResolver.RewardStateOf(deposit)
 90
 91	// Calculate internal rewards regardless of current tier status
 92	// The reward cache system will automatically handle periods with 0 rewards
 93	// This allows collecting rewards earned while the pool was in a tier,
 94	// while preventing new rewards after tier removal
 95	calculatedInternalRewards, calculatedInternalPenalties := rewardState.calculateInternalReward(lastCollectTime, param.CurrentTime)
 96
 97	warmupLen := len(deposit.Warmups())
 98	rewards := make([]Reward, warmupLen)
 99	for i := 0; i < warmupLen; i++ {
100		rewards[i] = Reward{
101			Internal:        calculatedInternalRewards[i],
102			InternalPenalty: calculatedInternalPenalties[i],
103			External:        make(map[string]int64),
104			ExternalPenalty: make(map[string]int64),
105		}
106	}
107	rewardState.reset()
108
109	lastExternalIncentiveUpdatedAt := depositResolver.LastExternalIncentiveUpdatedAt()
110
111	// update deposit's incentive list with new incentives created since last update
112	if lastExternalIncentiveUpdatedAt < param.CurrentTime {
113		// get new incentives created since last update
114		currentIncentiveIds := s.getExternalIncentiveIdsBy(poolPath, lastExternalIncentiveUpdatedAt, param.CurrentTime)
115
116		// add new created incentives to deposit
117		for _, incentiveId := range currentIncentiveIds {
118			deposit.AddExternalIncentiveId(incentiveId)
119		}
120
121		deposit.SetLastExternalIncentiveUpdatedAt(param.CurrentTime)
122	}
123
124	incentivesResolver := poolResolver.IncentivesResolver()
125
126	// Use deposit's indexed incentive IDs instead of iterating all pool incentives
127	deposit.IterateExternalIncentiveIds(func(incentiveId string) bool {
128		incentive, ok := incentivesResolver.Get(incentiveId)
129		if !ok {
130			return false
131		}
132
133		incentiveResolver := NewExternalIncentiveResolver(incentive)
134
135		// Check if incentive is active during this specific collection period
136		if !incentiveResolver.IsStarted(param.CurrentTime) {
137			return false
138		}
139
140		// External incentivized pool.
141		// Calculate reward for each warmup using per-incentive lastCollectTime
142		externalLastCollectTime := depositResolver.ExternalRewardLastCollectTime(incentiveId)
143		externalReward, externalPenalty := rewardState.calculateExternalReward(externalLastCollectTime, param.CurrentTime, incentive)
144
145		for i := range externalReward {
146			if externalReward[i] > 0 || externalPenalty[i] > 0 {
147				rewards[i].External[incentiveId] = externalReward[i]
148				rewards[i].ExternalPenalty[incentiveId] = externalPenalty[i]
149			}
150		}
151
152		rewardState.reset()
153
154		return false
155	})
156
157	return rewards
158}
159
160// calculates internal unclaimable reward for the pool
161func (s *stakerV1) processUnClaimableReward(poolPath string, endTimestamp int64) int64 {
162	pool, ok := s.getPools().Get(poolPath)
163	if !ok {
164		return 0
165	}
166	poolResolver := NewPoolResolver(pool)
167	return poolResolver.processUnclaimableReward(endTimestamp)
168}
169
170// update deposit's incentive list with new incentives created since last update
171func (s *stakerV1) getExternalIncentiveIdsBy(poolPath string, startTime, endTime int64) []string {
172	currentIncentiveIds := make([]string, 0)
173
174	incentivesByTime := s.getExternalIncentivesByCreationTime()
175
176	incentivesByTime.Iterate(startTime, endTime, func(_ int64, value any) bool {
177		// Value is a slice of incentive IDs (handles timestamp collisions)
178		poolIncentiveIds, ok := value.(*avl.Tree)
179		if !ok {
180			return false
181		}
182
183		incentiveIdsValue, exists := poolIncentiveIds.Get(poolPath)
184		if !exists {
185			return false
186		}
187
188		incentiveIds, ok := incentiveIdsValue.([]string)
189		if !ok {
190			return false
191		}
192
193		currentIncentiveIds = append(currentIncentiveIds, incentiveIds...)
194
195		return false
196	})
197
198	return currentIncentiveIds
199}
200
201// getInitialCollectTime determines the initial collection time for an incentive
202// by taking the maximum of the deposit's stake time and the incentive's start time.
203// This ensures rewards are only calculated from when both conditions are met:
204// - The position must be staked (deposit.stakeTime)
205// - The incentive must be active (incentive.startTimestamp)
206//
207// This function is used for lazy initialization when a position collects
208// from an incentive for the first time, avoiding the need to iterate through
209// all deposits when a new incentive is created.
210func getInitialCollectTime(deposit *sr.Deposit, incentive *sr.ExternalIncentive) int64 {
211	if deposit.StakeTime() > incentive.StartTimestamp() {
212		return deposit.StakeTime()
213	}
214	return incentive.StartTimestamp()
215}