Search Apps Documentation Source Content File Folder Download Copy Actions Download

reward_manager.gno

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