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

reward_manager.gno

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