state.gno
19.22 Kb · 611 lines
1package staker
2
3import (
4 "math"
5
6 gnsmath "gno.land/p/gnoswap/gnsmath"
7 bptree "gno.land/p/nt/bptree/v0"
8 ufmt "gno.land/p/nt/ufmt/v0"
9
10 "gno.land/r/gnoswap/emission"
11 "gno.land/r/gnoswap/gov/staker"
12 pf "gno.land/r/gnoswap/protocol_fee"
13)
14
15// setUnDelegationLockupPeriod updates the undelegation lockup period.
16// This affects all future undelegation operations.
17//
18// Parameters:
19// - period: new lockup period in seconds
20func (g *govStakerV1) setUnDelegationLockupPeriod(_ int, rlm realm, period int64) {
21 if err := g.store.SetUnDelegationLockupPeriod(0, rlm, period); err != nil {
22 panic(err)
23 }
24}
25
26// nextDelegationID generates and returns the next unique delegation ID.
27//
28// Returns:
29// - int64: next available delegation ID
30func (g *govStakerV1) nextDelegationID() int64 {
31 counter := g.store.GetDelegationCounter()
32
33 return counter.Next()
34}
35
36// getDelegation retrieves a delegation by its ID.
37//
38// Parameters:
39// - delegationID: unique identifier of the delegation
40//
41// Returns:
42// - *Delegation: delegation instance or nil if not found
43func (g *govStakerV1) getDelegation(delegationID int64) *staker.Delegation {
44 delegation, exists := g.store.GetDelegation(delegationID)
45 if !exists {
46 return nil
47 }
48 return delegation
49}
50
51// setDelegation stores or updates a delegation in the storage tree.
52//
53// Parameters:
54// - delegationID: unique identifier of the delegation
55// - delegation: delegation instance to store
56//
57// Returns:
58// - bool: true if successfully stored
59func (g *govStakerV1) setDelegation(_ int, rlm realm, delegationID int64, delegation *staker.Delegation) bool {
60 if err := g.store.SetDelegation(0, rlm, delegationID, delegation); err != nil {
61 return false
62 }
63 return true
64}
65
66// addDelegation adds a new delegation to storage and updates the delegation manager.
67//
68// Parameters:
69// - delegationID: unique identifier of the delegation
70// - delegation: delegation instance to add
71//
72// Returns:
73// - bool: true if successfully added
74func (g *govStakerV1) addDelegation(_ int, rlm realm, delegationID int64, delegation *staker.Delegation) bool {
75 if ok := g.setDelegation(0, rlm, delegationID, delegation); !ok {
76 return false
77 }
78
79 // Update delegation manager
80 delegationManager := g.store.GetDelegationManager()
81 resolvedManager := NewDelegationManagerResolver(delegationManager)
82 resolvedManager.addDelegation(
83 delegation.DelegateFrom(),
84 delegation.DelegateTo(),
85 delegationID,
86 )
87 if err := g.store.SetDelegationManager(0, rlm, delegationManager); err != nil {
88 return false
89 }
90
91 return true
92}
93
94// removeDelegation removes a delegation from storage and updates the delegation manager.
95//
96// Parameters:
97// - delegationID: unique identifier of the delegation to remove
98//
99// Returns:
100// - bool: true if successfully removed
101func (g *govStakerV1) removeDelegation(_ int, rlm realm, delegationID int64) bool {
102 delegation := g.getDelegation(delegationID)
103 if delegation == nil {
104 return false
105 }
106
107 // Remove from store
108 if err := g.store.RemoveDelegation(0, rlm, delegationID); err != nil {
109 return false
110 }
111
112 // Update delegation manager
113 delegationManager := g.store.GetDelegationManager()
114 resolvedManager := NewDelegationManagerResolver(delegationManager)
115 resolvedManager.removeDelegation(
116 delegation.DelegateFrom(),
117 delegation.DelegateTo(),
118 delegationID,
119 )
120 if err := g.store.SetDelegationManager(0, rlm, delegationManager); err != nil {
121 return false
122 }
123
124 return true
125}
126
127// getUserDelegations retrieves all delegations for a specific user.
128//
129// Parameters:
130// - user: user's address
131//
132// Returns:
133// - *bptree.BPTree: tree of user's delegations
134func (g *govStakerV1) getUserDelegations(user address) *bptree.BPTree {
135 delegationManager := g.store.GetDelegationManager()
136
137 userDelegations, exists := delegationManager.GetDelegatorDelegations(user.String())
138 if !exists {
139 return staker.NewUserDelegationTree()
140 }
141
142 return userDelegations
143}
144
145// getUserDelegationsWithDelegatee retrieves all delegations from a user to a specific delegatee.
146//
147// Parameters:
148// - user: user's address
149// - delegatee: delegatee's address
150//
151// Returns:
152// - []int64: list of user's delegation IDs to the delegatee
153func (g *govStakerV1) getUserDelegationIDsWithDelegatee(user address, delegatee address) []int64 {
154 delegationManager := g.store.GetDelegationManager()
155
156 userDelegations, exists := delegationManager.GetDelegatorDelegations(user.String())
157 if !exists {
158 return nil
159 }
160
161 delegateeStr := delegatee.String()
162 delegationIDs := userDelegations.Get(delegateeStr)
163 if delegationIDs == nil {
164 return nil
165 }
166
167 ids, ok := delegationIDs.([]int64)
168 if !ok {
169 return nil
170 }
171
172 return ids
173}
174
175// addDelegationRecord records a delegation change in the history.
176// Updates both total and user delegation histories with cumulative values.
177//
178// Parameters:
179// - delegateeAddr: address of the delegatee
180// - amount: amount change (positive for delegate, negative for undelegate)
181// - timestamp: timestamp of the delegation change
182func (g *govStakerV1) addDelegationRecord(_ int, rlm realm, delegateeAddr address, amount int64, timestamp int64) {
183 // Update total delegation history
184 g.updateTotalDelegationHistory(0, rlm, amount, timestamp)
185
186 // Update user delegation history
187 g.updateUserDelegationHistory(0, rlm, delegateeAddr, amount, timestamp)
188}
189
190// updateTotalDelegationHistory updates the total delegation history with cumulative value.
191//
192// Parameters:
193// - amount: amount change (positive for delegate, negative for undelegate)
194// - timestamp: timestamp of the change
195func (g *govStakerV1) updateTotalDelegationHistory(_ int, rlm realm, amount int64, timestamp int64) {
196 history := g.store.GetTotalDelegationHistory()
197
198 // Get current total from the most recent entry
199 currentTotal := g.getLatestTotalDelegation(history)
200 newTotal := gnsmath.SafeAddInt64(currentTotal, amount)
201
202 if newTotal < 0 {
203 newTotal = 0
204 }
205
206 history.Set(timestamp, newTotal)
207
208 if err := g.store.SetTotalDelegationHistory(0, rlm, history); err != nil {
209 panic(err)
210 }
211}
212
213// updateUserDelegationHistory updates the user delegation history with cumulative values.
214// Structure: single BPTree keyed by composite key "addrStr|paddedTimestamp" -> int64
215//
216// Parameters:
217// - delegateeAddr: address of the delegatee
218// - amount: amount change (positive for delegate, negative for undelegate)
219// - timestamp: timestamp of the change
220func (g *govStakerV1) updateUserDelegationHistory(_ int, rlm realm, delegateeAddr address, amount int64, timestamp int64) {
221 history := g.store.GetUserDelegationHistory()
222 addrStr := delegateeAddr.String()
223
224 // Get current amount from the most recent entry for this user
225 currentAmount := g.getLatestUserDelegationByAddress(history, addrStr)
226 newAmount := gnsmath.SafeAddInt64(currentAmount, amount)
227 if newAmount < 0 {
228 newAmount = 0
229 }
230
231 // Store new cumulative value under composite key
232 history.Set(makeUserHistoryKey(addrStr, timestamp), newAmount)
233
234 if err := g.store.SetUserDelegationHistory(0, rlm, history); err != nil {
235 panic(err)
236 }
237}
238
239// getLatestTotalDelegation gets the latest total delegation amount from history.
240func (g *govStakerV1) getLatestTotalDelegation(history *staker.UintTree) int64 {
241 if history.Size() == 0 {
242 return 0
243 }
244
245 latestTotal := int64(0)
246
247 history.ReverseIterate(0, math.MaxInt64, func(key int64, value any) bool {
248 totalInt, ok := value.(int64)
249 if !ok {
250 panic(ufmt.Sprintf("invalid total type: %T", value))
251 }
252
253 latestTotal = totalInt
254
255 return true // stop after first (most recent) entry
256 })
257
258 return latestTotal
259}
260
261// getLatestUserDelegationByAddress gets the latest delegation amount for a user
262// from the composite-keyed user delegation history.
263func (g *govStakerV1) getLatestUserDelegationByAddress(history *bptree.BPTree, addrStr string) int64 {
264 lo, hi := userHistoryKeyRange(addrStr)
265
266 latestAmount := int64(0)
267
268 history.ReverseIterate(lo, hi, func(_ string, value any) bool {
269 amountInt, ok := value.(int64)
270 if !ok {
271 panic(ufmt.Sprintf("invalid amount type: %T", value))
272 }
273
274 latestAmount = amountInt
275
276 return true // stop after first (most recent) entry
277 })
278
279 return latestAmount
280}
281
282// addStakeEmissionReward adds stake to emission reward tracking for an address.
283// This method updates the emission reward distribution state and adds stake for the specified address.
284//
285// Parameters:
286// - address: staker's address
287// - amount: amount of stake to add
288// - currentTimestamp: current timestamp
289func (g *govStakerV1) addStakeEmissionReward(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
290 distributedAmount := emission.GetAccuDistributedToGovStaker()
291
292 emissionRewardManager := g.store.GetEmissionRewardManager()
293 resolvedManager := NewEmissionRewardManagerResolver(emissionRewardManager)
294
295 if err := resolvedManager.updateAccumulatedRewardX128PerStake(distributedAmount, currentTimestamp); err != nil {
296 return err
297 }
298
299 if err := resolvedManager.addStake(address, amount, currentTimestamp); err != nil {
300 return err
301 }
302
303 err := g.store.SetEmissionRewardManager(0, rlm, emissionRewardManager)
304 if err != nil {
305 return err
306 }
307
308 // Emit the emission reward accumulation
309 emitUpdateEmissionRewardAccumulation(emissionRewardManager)
310
311 return nil
312}
313
314// removeStakeEmissionReward removes stake from emission reward tracking for an address.
315// This method updates the emission reward distribution state and removes stake for the specified address.
316//
317// Parameters:
318// - address: staker's address
319// - amount: amount of stake to remove
320// - currentTimestamp: current timestamp
321func (g *govStakerV1) removeStakeEmissionReward(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
322 distributedAmount := emission.GetAccuDistributedToGovStaker()
323
324 emissionRewardManager := g.store.GetEmissionRewardManager()
325 resolvedManager := NewEmissionRewardManagerResolver(emissionRewardManager)
326
327 if err := resolvedManager.updateAccumulatedRewardX128PerStake(distributedAmount, currentTimestamp); err != nil {
328 return err
329 }
330
331 if err := resolvedManager.removeStake(address, amount, currentTimestamp); err != nil {
332 return err
333 }
334
335 if err := g.store.SetEmissionRewardManager(0, rlm, emissionRewardManager); err != nil {
336 return err
337 }
338
339 // Emit the emission reward accumulation
340 emitUpdateEmissionRewardAccumulation(emissionRewardManager)
341
342 return nil
343}
344
345// claimRewardsEmissionReward claims emission rewards for an address.
346// This method updates the emission reward distribution state and processes reward claiming.
347//
348// Parameters:
349// - address: staker's address claiming rewards
350// - currentTimestamp: current timestamp
351//
352// Returns:
353// - int64: amount of emission rewards claimed
354// - error: nil on success, error if claiming fails
355func (g *govStakerV1) claimRewardsEmissionReward(_ int, rlm realm, address string, currentTimestamp int64) (int64, error) {
356 distributedAmount := emission.GetAccuDistributedToGovStaker()
357
358 emissionRewardManager := g.store.GetEmissionRewardManager()
359 resolvedManager := NewEmissionRewardManagerResolver(emissionRewardManager)
360
361 if err := resolvedManager.updateAccumulatedRewardX128PerStake(distributedAmount, currentTimestamp); err != nil {
362 return 0, err
363 }
364
365 amount, err := resolvedManager.claimRewards(address, currentTimestamp)
366 if err != nil {
367 return 0, err
368 }
369
370 if err := g.store.SetEmissionRewardManager(0, rlm, emissionRewardManager); err != nil {
371 return 0, err
372 }
373
374 // Emit the emission reward accumulation
375 emitUpdateEmissionRewardAccumulation(emissionRewardManager)
376
377 return amount, nil
378}
379
380// removeLaunchpadProjectDeposit removes a launchpad project deposit record.
381//
382// Parameters:
383// - ownerAddress: project owner's address identifier
384//
385// Returns:
386// - bool: true if successfully removed
387func (g *govStakerV1) removeLaunchpadProjectDeposit(ownerAddress string) bool {
388 launchpadProjectDeposits := g.store.GetLaunchpadProjectDeposits()
389 return launchpadProjectDeposits.RemoveDeposit(ownerAddress)
390}
391
392// addStakeProtocolFeeReward adds stake to protocol fee reward tracking for an address.
393// This method distributes protocol fees and updates the protocol fee reward state.
394//
395// Parameters:
396// - address: staker's address
397// - amount: amount of stake to add
398// - currentTimestamp: current timestamp
399func (g *govStakerV1) addStakeProtocolFeeReward(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
400 pf.DistributeProtocolFee(cross(rlm))
401
402 distributedAmounts := g.getDistributedProtocolFees()
403
404 protocolFeeRewardManager := g.store.GetProtocolFeeRewardManager()
405 resolvedManager := NewProtocolFeeRewardManagerResolver(protocolFeeRewardManager)
406
407 if err := resolvedManager.updateAccumulatedProtocolFeeX128PerStake(distributedAmounts, currentTimestamp); err != nil {
408 return err
409 }
410
411 if err := resolvedManager.addStake(address, amount, currentTimestamp); err != nil {
412 return err
413 }
414
415 err := g.store.SetProtocolFeeRewardManager(0, rlm, protocolFeeRewardManager)
416 if err != nil {
417 return err
418 }
419
420 // Emit the protocol fee reward accumulation
421 emitUpdateProtocolFeeRewardAccumulation(protocolFeeRewardManager, distributedAmounts)
422
423 return nil
424}
425
426// removeStakeProtocolFeeReward removes stake from protocol fee reward tracking for an address.
427// This method distributes protocol fees and updates the protocol fee reward state.
428//
429// Parameters:
430// - address: staker's address
431// - amount: amount of stake to remove
432// - currentTimestamp: current timestamp
433func (g *govStakerV1) removeStakeProtocolFeeReward(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
434 pf.DistributeProtocolFee(cross(rlm))
435
436 distributedAmounts := g.getDistributedProtocolFees()
437
438 protocolFeeRewardManager := g.store.GetProtocolFeeRewardManager()
439 resolvedManager := NewProtocolFeeRewardManagerResolver(protocolFeeRewardManager)
440
441 if err := resolvedManager.updateAccumulatedProtocolFeeX128PerStake(distributedAmounts, currentTimestamp); err != nil {
442 return err
443 }
444
445 if err := resolvedManager.removeStake(address, amount, currentTimestamp); err != nil {
446 return err
447 }
448
449 if err := g.store.SetProtocolFeeRewardManager(0, rlm, protocolFeeRewardManager); err != nil {
450 return err
451 }
452
453 // Emit the protocol fee reward accumulation
454 emitUpdateProtocolFeeRewardAccumulation(protocolFeeRewardManager, distributedAmounts)
455
456 return nil
457}
458
459// claimRewardsProtocolFeeReward claims protocol fee rewards for an address.
460// This method distributes protocol fees and processes reward claiming for all token types.
461//
462// Parameters:
463// - address: staker's address claiming rewards
464// - currentTimestamp: current timestamp
465//
466// Returns:
467// - map[string]int64: protocol fee rewards claimed by token
468// - error: nil on success, error if claiming fails
469func (g *govStakerV1) claimRewardsProtocolFeeReward(_ int, rlm realm, address string, currentTimestamp int64) (map[string]int64, error) {
470 pf.DistributeProtocolFee(cross(rlm))
471
472 distributedAmounts := g.getDistributedProtocolFees()
473
474 protocolFeeRewardManager := g.store.GetProtocolFeeRewardManager()
475 resolvedManager := NewProtocolFeeRewardManagerResolver(protocolFeeRewardManager)
476
477 if err := resolvedManager.updateAccumulatedProtocolFeeX128PerStake(distributedAmounts, currentTimestamp); err != nil {
478 return nil, err
479 }
480
481 rewards, err := resolvedManager.claimRewards(address, currentTimestamp)
482 if err != nil {
483 return nil, err
484 }
485
486 if err := g.store.SetProtocolFeeRewardManager(0, rlm, protocolFeeRewardManager); err != nil {
487 return nil, err
488 }
489
490 // Emit the protocol fee reward accumulation
491 emitUpdateProtocolFeeRewardAccumulation(protocolFeeRewardManager, distributedAmounts)
492
493 return rewards, nil
494}
495
496// getDistributedProtocolFees retrieves the current distributed protocol fee amounts for all tokens.
497// This method queries the protocol fee contract for accumulated distributions.
498//
499// Returns:
500// - map[string]int64: distributed amounts by token path
501func (g *govStakerV1) getDistributedProtocolFees() map[string]int64 {
502 return pf.GetActualDistributedToGovStaker()
503}
504
505// getLaunchpadProjectDeposit retrieves the deposit amount for a launchpad project.
506//
507// Parameters:
508// - ownerAddress: project owner's address identifier
509//
510// Returns:
511// - int64: deposit amount
512// - bool: true if project exists, false otherwise
513func (g *govStakerV1) getLaunchpadProjectDeposit(ownerAddress string) (int64, bool) {
514 launchpadDeposits := g.store.GetLaunchpadProjectDeposits()
515 resolvedDeposits := NewLaunchpadProjectDepositsResolver(launchpadDeposits)
516 return resolvedDeposits.getLaunchpadProjectDeposit(ownerAddress)
517}
518
519// setLaunchpadProjectDeposit sets the deposit amount for a launchpad project.
520//
521// Parameters:
522// - ownerAddress: project owner's address identifier
523// - deposit: deposit amount to set
524//
525// Returns:
526// - bool: true if successfully set
527func (g *govStakerV1) setLaunchpadProjectDeposit(_ int, rlm realm, ownerAddress string, deposit int64) bool {
528 launchpadDeposits := g.store.GetLaunchpadProjectDeposits()
529 resolvedDeposits := NewLaunchpadProjectDepositsResolver(launchpadDeposits)
530 resolvedDeposits.setLaunchpadProjectDeposit(ownerAddress, deposit)
531 if err := g.store.SetLaunchpadProjectDeposits(0, rlm, launchpadDeposits); err != nil {
532 return false
533 }
534 return true
535}
536
537// addStakeFromLaunchpad adds stake for a launchpad project and updates reward tracking.
538// This method creates a special reward ID for launchpad projects and manages their deposit tracking.
539//
540// Parameters:
541// - address: project wallet address
542// - amount: amount of stake to add
543// - currentTimestamp: current timestamp
544func (g *govStakerV1) addStakeFromLaunchpad(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
545 launchpadRewardID := g.makeLaunchpadRewardID(address)
546 err := g.addStakeEmissionReward(0, rlm, launchpadRewardID, amount, currentTimestamp)
547 if err != nil {
548 return err
549 }
550
551 err = g.addStakeProtocolFeeReward(0, rlm, launchpadRewardID, amount, currentTimestamp)
552 if err != nil {
553 return err
554 }
555
556 deposit, exists := g.getLaunchpadProjectDeposit(launchpadRewardID)
557 if !exists {
558 deposit = 0
559 }
560
561 deposit = gnsmath.SafeAddInt64(deposit, amount)
562 g.setLaunchpadProjectDeposit(0, rlm, launchpadRewardID, deposit)
563
564 return nil
565}
566
567// removeStakeFromLaunchpad removes stake for a launchpad project and updates reward tracking.
568// This method manages launchpad project deposit tracking and ensures non-negative deposits.
569//
570// Parameters:
571// - address: project wallet address
572// - amount: amount of stake to remove
573// - currentTimestamp: current timestamp
574func (g *govStakerV1) removeStakeFromLaunchpad(_ int, rlm realm, address string, amount int64, currentTimestamp int64) error {
575 launchpadRewardID := g.makeLaunchpadRewardID(address)
576 err := g.removeStakeEmissionReward(0, rlm, launchpadRewardID, amount, currentTimestamp)
577 if err != nil {
578 return err
579 }
580
581 err = g.removeStakeProtocolFeeReward(0, rlm, launchpadRewardID, amount, currentTimestamp)
582 if err != nil {
583 return err
584 }
585
586 deposit, exists := g.getLaunchpadProjectDeposit(launchpadRewardID)
587 if !exists {
588 deposit = 0
589 }
590
591 deposit = gnsmath.SafeSubInt64(deposit, amount)
592 if deposit < 0 {
593 deposit = 0
594 }
595
596 g.setLaunchpadProjectDeposit(0, rlm, launchpadRewardID, deposit)
597
598 return nil
599}
600
601// makeLaunchpadRewardID creates a special reward identifier for launchpad projects.
602// This ensures launchpad project rewards are tracked separately from regular user stakes.
603//
604// Parameters:
605// - address: project wallet address
606//
607// Returns:
608// - string: formatted launchpad reward ID
609func (g *govStakerV1) makeLaunchpadRewardID(address string) string {
610 return "launchpad:" + address
611}