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

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