Search Apps Documentation Source Content File Folder Download Copy Actions Download

reward_manager.gno

8.57 Kb ยท 289 lines
  1package v1
  2
  3import (
  4	"gno.land/p/nt/ufmt"
  5	"gno.land/r/gnoswap/launchpad"
  6
  7	u256 "gno.land/p/gnoswap/uint256"
  8)
  9
 10// Helper functions for RewardManager
 11
 12func isRewardManagerInitialized(r *launchpad.RewardManager) bool {
 13	return r.Rewards().Size() > 0
 14}
 15
 16func getAccumulatedReward(r *launchpad.RewardManager) int64 {
 17	res := u256.Zero().Rsh(r.AccumulatedRewardPerDepositX128(), 128)
 18	return safeConvertToInt64(res)
 19}
 20
 21func getDepositRewardState(r *launchpad.RewardManager, depositId string) (*launchpad.RewardState, error) {
 22	rewardStateI, exists := r.Rewards().Get(depositId)
 23	if !exists {
 24		return nil, makeErrorWithDetails(errNotExistDeposit, ufmt.Sprintf("(%s)", depositId))
 25	}
 26
 27	rewardState, ok := rewardStateI.(*launchpad.RewardState)
 28	if !ok {
 29		return nil, ufmt.Errorf("failed to cast rewardState to *launchpad.RewardState: %T", rewardStateI)
 30	}
 31
 32	return rewardState, nil
 33}
 34
 35func calculateRewardPerDepositX128(r *launchpad.RewardManager, rewardPerSecondX128 *u256.Uint, totalStaked int64, currentTime int64) (*u256.Uint, error) {
 36	accumulatedTime := r.AccumulatedTime()
 37	if r.DistributeStartTime() > accumulatedTime {
 38		accumulatedTime = r.DistributeStartTime()
 39	}
 40
 41	// not started yet
 42	if currentTime < accumulatedTime {
 43		return u256.Zero(), nil
 44	}
 45
 46	// past distribute end time
 47	if accumulatedTime > r.DistributeEndTime() {
 48		return u256.Zero(), nil
 49	}
 50
 51	// past distribute end time, set to distribute end time
 52	if currentTime > r.DistributeEndTime() {
 53		currentTime = r.DistributeEndTime()
 54	}
 55
 56	if rewardPerSecondX128.IsZero() {
 57		return nil, makeErrorWithDetails(
 58			errNoLeftReward,
 59			ufmt.Sprintf("rewardPerSecond(%d)", rewardPerSecondX128),
 60		)
 61	}
 62
 63	// no left reward
 64	if totalStaked == 0 {
 65		return u256.Zero(), nil
 66	}
 67
 68	// timeDuration * rewardPerSecond / totalStaked
 69	timeDuration := currentTime - accumulatedTime
 70	rewardPerDepositX128 := u256.MulDiv(
 71		u256.NewUintFromInt64(timeDuration),
 72		rewardPerSecondX128,
 73		u256.NewUintFromInt64(totalStaked),
 74	)
 75
 76	return rewardPerDepositX128, nil
 77}
 78
 79func addRewardStateByDeposit(r *launchpad.RewardManager, deposit *launchpad.Deposit) *launchpad.RewardState {
 80	claimableTime := deposit.CreatedAt() + r.RewardClaimableDuration()
 81	if claimableTime > r.DistributeEndTime() {
 82		claimableTime = r.DistributeEndTime()
 83	}
 84
 85	rewardState := newRewardState(
 86		r.AccumulatedRewardPerDepositX128(),
 87		deposit.DepositAmount(),
 88		deposit.CreatedAt(),
 89		r.DistributeEndTime(),
 90		claimableTime,
 91	)
 92
 93	// if the first deposit, set the distribute start time
 94	if !isRewardManagerInitialized(r) {
 95		rewardState.SetDistributeStartTime(r.DistributeStartTime())
 96		rewardState.SetDistributeEndTime(r.DistributeEndTime())
 97		rewardState.SetAccumulatedTime(r.DistributeStartTime())
 98		rewardState.SetPriceDebtX128(u256.Zero())
 99	}
100
101	return addRewardState(r, deposit, rewardState)
102}
103
104func addRewardState(r *launchpad.RewardManager, deposit *launchpad.Deposit, rewardState *launchpad.RewardState) *launchpad.RewardState {
105	rewards := r.Rewards()
106	rewards.Set(deposit.ID(), rewardState)
107	r.SetRewards(rewards)
108
109	return rewardState
110}
111
112// removeRewardState removes a reward state from the reward manager when a deposit is withdrawn.
113// This improves iteration performance and ensures accurate pending reward calculations.
114func removeRewardState(r *launchpad.RewardManager, depositId string) {
115	rewards := r.Rewards()
116	rewards.Remove(depositId)
117	r.SetRewards(rewards)
118}
119
120func addRewardPerDepositX128(r *launchpad.RewardManager, rewardPerDepositX128 *u256.Uint, currentHeight, currentTime int64) error {
121	if rewardPerDepositX128.IsZero() {
122		return nil
123	}
124
125	if r.AccumulatedTime() > currentTime || r.DistributeStartTime() > currentTime {
126		return nil
127	}
128
129	if currentTime > r.DistributeEndTime() {
130		currentTime = r.DistributeEndTime()
131	}
132
133	accumulated := u256.Zero().Add(r.AccumulatedRewardPerDepositX128(), rewardPerDepositX128)
134	r.SetAccumulatedRewardPerDepositX128(accumulated)
135	r.SetAccumulatedHeight(currentHeight)
136	r.SetAccumulatedTime(currentTime)
137
138	return nil
139}
140
141// updateRewardPerDepositX128 updates the reward per deposit state.
142// This function calculates and updates the accumulated reward per deposit
143// based on the current total deposit amount and height.
144//
145// Parameters:
146// - totalDepositAmount (int64): Current total deposit amount
147// - height (int64): Current blockchain height
148// - time (int64): Current timestamp
149//
150// Returns:
151// - error: If the update fails
152func updateRewardPerDepositX128(r *launchpad.RewardManager, totalDepositAmount int64, currentHeight, currentTime int64) error {
153	if currentTime <= 0 {
154		return makeErrorWithDetails(errInvalidTime, "time must be positive")
155	}
156
157	// Calculate and update rewards
158	rewardPerDepositX128, err := calculateRewardPerDepositX128(
159		r,
160		r.DistributeAmountPerSecondX128(),
161		totalDepositAmount,
162		currentTime,
163	)
164	if err != nil {
165		return err
166	}
167
168	err = addRewardPerDepositX128(r, rewardPerDepositX128, currentHeight, currentTime)
169	if err != nil {
170		return err
171	}
172
173	return nil
174}
175
176func updateDistributeAmountPerSecondX128(r *launchpad.RewardManager, totalDistributeAmount int64, distributeStartTime int64, distributeEndTime int64) {
177	// Use time duration for per-second calculation
178	timeDuration := distributeEndTime - distributeStartTime
179	if timeDuration <= 0 {
180		return
181	}
182
183	totalDistributeAmountX128 := u256.Zero().Lsh(
184		u256.NewUintFromInt64(totalDistributeAmount),
185		128,
186	)
187
188	// Divide by time duration in seconds
189	amountPerSecondX128 := u256.Zero().Div(
190		totalDistributeAmountX128,
191		u256.NewUintFromInt64(timeDuration),
192	)
193
194	r.SetDistributeAmountPerSecondX128(amountPerSecondX128)
195	r.SetDistributeStartTime(distributeStartTime)
196	r.SetDistributeEndTime(distributeEndTime)
197}
198
199// collectReward processes the reward collection for a specific deposit.
200// This function ensures that the reward collection is valid and updates
201// the claimed amount accordingly.
202//
203// Parameters:
204// - depositId (string): The ID of the deposit
205// - currentTime (int64): Current timestamp
206//
207// Returns:
208// - int64: The amount of reward collected
209// - error: If the collection fails
210func collectReward(r *launchpad.RewardManager, depositId string, currentTime int64) (int64, error) {
211	if currentTime < r.AccumulatedTime() {
212		return 0, makeErrorWithDetails(
213			errInvalidRewardState,
214			ufmt.Sprintf("currentTime %d is less than AccumulatedTime %d", currentTime, r.AccumulatedTime()),
215		)
216	}
217
218	rewardState, err := getDepositRewardState(r, depositId)
219	if err != nil {
220		return 0, err
221	}
222
223	if !isRewardStateClaimable(rewardState, currentTime) {
224		return 0, makeErrorWithDetails(
225			errInvalidRewardState,
226			ufmt.Sprintf("currentTime %d is less than claimableTime %d", currentTime, rewardState.ClaimableTime()),
227		)
228	}
229
230	if currentTime < rewardState.DistributeStartTime() {
231		return 0, makeErrorWithDetails(
232			errInvalidRewardState,
233			ufmt.Sprintf("currentTime %d is less than DistributeStartTime %d", currentTime, rewardState.DistributeStartTime()),
234		)
235	}
236
237	claimableReward := calculateClaimableReward(rewardState, r.AccumulatedRewardPerDepositX128())
238	if claimableReward == 0 {
239		return 0, nil
240	}
241
242	rewardState.SetClaimedAmount(rewardState.ClaimedAmount() + claimableReward)
243	rewards := r.Rewards()
244	rewards.Set(depositId, rewardState)
245	r.SetRewards(rewards)
246	r.SetTotalClaimedAmount(r.TotalClaimedAmount() + claimableReward)
247
248	return claimableReward, nil
249}
250
251// newRewardManager returns a pointer to a new RewardManager with the given values.
252func newRewardManager(
253	totalDistributeAmount int64,
254	distributeStartTime int64,
255	distributeEndTime int64,
256	rewardCollectableDuration int64,
257	currentHeight int64,
258	currentTime int64,
259) *launchpad.RewardManager {
260	manager := launchpad.NewRewardManager(totalDistributeAmount, distributeStartTime, distributeEndTime, rewardCollectableDuration, currentHeight, currentTime)
261
262	updateDistributeAmountPerSecondX128(manager, totalDistributeAmount, distributeStartTime, distributeEndTime)
263	updateRewardPerDepositX128(manager, 0, currentHeight, currentTime)
264
265	return manager
266}
267
268// calculateClaimableRewardsForActiveDeposits calculates the total claimable rewards
269// for all active deposits in the reward manager.
270// This is used when admin wants to reclaim undistributed rewards while some deposits remain.
271func calculateClaimableRewardsForActiveDeposits(r *launchpad.RewardManager) int64 {
272	totalClaimable := int64(0)
273	accumulatedReward := r.AccumulatedRewardPerDepositX128()
274
275	r.Rewards().Iterate("", "", func(depositId string, value any) bool {
276		rewardState, ok := value.(*launchpad.RewardState)
277		if !ok {
278			return false
279		}
280
281		// Calculate claimable reward for this deposit
282		claimable := calculateClaimableReward(rewardState, accumulatedReward)
283		totalClaimable = safeAddInt64(totalClaimable, claimable)
284
285		return false
286	})
287
288	return totalClaimable
289}