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}