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