delegation.gno
5.56 Kb · 189 lines
1package staker
2
3import (
4 "errors"
5
6 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
7
8 "gno.land/r/gnoswap/gov/staker"
9)
10
11const errCollectAmountExceedsCollectable = "amount to collect is greater than collectable amount"
12
13type DelegationResolver struct {
14 delegation *staker.Delegation
15}
16
17// NewDelegationResolver wraps a delegation in a resolver for derived amounts and withdrawals.
18//
19// Parameters:
20// - delegation: delegation state to resolve
21//
22// Returns:
23// - *DelegationResolver: resolver backed by delegation
24func NewDelegationResolver(delegation *staker.Delegation) *DelegationResolver {
25 return &DelegationResolver{delegation}
26}
27
28// Get returns the underlying delegation state.
29//
30// Returns:
31// - *staker.Delegation: wrapped delegation instance
32func (r *DelegationResolver) Get() *staker.Delegation {
33 return r.delegation
34}
35
36// DelegatedAmount returns total delegated amount minus amount already undelegated.
37//
38// Returns:
39// - int64: amount still delegated
40func (r *DelegationResolver) DelegatedAmount() int64 {
41 return gnsmath.SafeSubInt64(r.delegation.TotalDelegatedAmount(), r.delegation.UnDelegatedAmount())
42}
43
44// LockedAmount returns total delegated amount minus amount already collected.
45//
46// Returns:
47// - int64: amount still locked in the delegation
48func (r *DelegationResolver) LockedAmount() int64 {
49 return gnsmath.SafeSubInt64(r.delegation.TotalDelegatedAmount(), r.delegation.CollectedAmount())
50}
51
52// IsEmpty reports whether no locked delegation amount remains.
53//
54// Returns:
55// - bool: true when LockedAmount is zero, otherwise false
56func (r *DelegationResolver) IsEmpty() bool {
57 return r.LockedAmount() == 0
58}
59
60// CollectableAmount calculates the total amount that can be collected at the given time.
61//
62// Parameters:
63// - currentTime: current Unix timestamp used to evaluate each withdrawal's lockup
64//
65// Returns:
66// - total: sum of all withdrawal amounts currently available for collection
67func (r *DelegationResolver) CollectableAmount(currentTime int64) (total int64) {
68 for _, withdraw := range r.delegation.Withdraws() {
69 total = gnsmath.SafeAddInt64(total, NewDelegationWithdrawResolver(&withdraw).CollectableAmount(currentTime))
70 }
71
72 return total
73}
74
75// UnDelegate processes an undelegation with a time-based lockup period.
76//
77// Parameters:
78// - amount: amount to undelegate and place into a withdrawal
79// - currentHeight: block height at which the undelegation is recorded
80// - currentTimestamp: Unix timestamp at which the undelegation is recorded
81// - unDelegationLockupPeriod: lockup duration in seconds before the withdrawal is collectible
82func (r *DelegationResolver) UnDelegate(
83 amount, currentHeight, currentTimestamp, unDelegationLockupPeriod int64,
84) {
85 r.delegation.SetUnDelegateAmount(gnsmath.SafeAddInt64(r.delegation.UnDelegatedAmount(), amount))
86
87 withdraw := NewDelegationWithdraw(
88 r.delegation.ID(),
89 amount,
90 currentHeight,
91 currentTimestamp,
92 unDelegationLockupPeriod,
93 )
94 r.delegation.AddWithdraw(withdraw)
95}
96
97// UnDelegateWithoutLockup processes an immediate undelegation without lockup.
98//
99// Parameters:
100// - amount: amount to undelegate and mark as collected immediately
101// - currentHeight: current block height context; retained for API symmetry and unused by this immediate path
102// - currentTime: current Unix timestamp context; retained for API symmetry and unused by this immediate path
103func (r *DelegationResolver) UnDelegateWithoutLockup(
104 amount, currentHeight, currentTime int64,
105) {
106 r.delegation.SetUnDelegateAmount(gnsmath.SafeAddInt64(r.delegation.UnDelegatedAmount(), amount))
107 r.delegation.SetCollectedAmount(gnsmath.SafeAddInt64(r.delegation.CollectedAmount(), amount))
108}
109
110// processCollection handles the actual collection logic
111func (r *DelegationResolver) processCollection(currentTime int64) (int64, error) {
112 collectedAmount := int64(0)
113 withdraws := r.delegation.Withdraws()
114
115 for i := range withdraws {
116 resolver := NewDelegationWithdrawResolver(&withdraws[i])
117
118 collectableAmount := resolver.CollectableAmount(currentTime)
119 if collectableAmount <= 0 {
120 continue
121 }
122
123 err := resolver.Collect(collectableAmount, currentTime)
124 if err != nil {
125 return 0, err
126 }
127
128 updatedAmount, err := addToCollectedAmount(r.delegation.CollectedAmount(), collectableAmount)
129 if err != nil {
130 return 0, err
131 }
132
133 r.delegation.SetCollectedAmount(updatedAmount)
134 collectedAmount = gnsmath.SafeAddInt64(collectedAmount, collectableAmount)
135 }
136
137 // Skip filtering if nothing was collected
138 if collectedAmount == 0 {
139 return collectedAmount, nil
140 }
141
142 currentIndex := 0
143
144 for i := range withdraws {
145 if !withdraws[i].IsCollected() {
146 r.delegation.SetWithdraw(currentIndex, withdraws[i])
147 currentIndex++
148 }
149 }
150
151 r.delegation.SetWithdraws(withdraws[:currentIndex])
152
153 return collectedAmount, nil
154}
155
156func addToCollectedAmount(collectedAmount, amount int64) (int64, error) {
157 if amount < 0 {
158 return 0, errors.New(errAmountMustBeNonNegative)
159 }
160 return gnsmath.SafeAddInt64(collectedAmount, amount), nil
161}
162
163// NewDelegation creates a new delegation.
164// This is a convenience wrapper around staker.NewDelegation.
165//
166// Parameters:
167// - id: delegation ID
168// - delegateFrom: delegator's address
169// - delegateTo: delegatee's address
170// - delegateAmount: amount to delegate
171// - createdHeight: creation block height
172// - createdAt: creation timestamp
173//
174// Returns:
175// - *staker.Delegation: new delegation instance
176func NewDelegation(
177 id int64,
178 delegateFrom, delegateTo address,
179 delegateAmount, createdHeight, createdAt int64,
180) *staker.Delegation {
181 return staker.NewDelegation(
182 id,
183 delegateFrom,
184 delegateTo,
185 delegateAmount,
186 createdHeight,
187 createdAt,
188 )
189}