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}