Skip to content

Commit f8678d7

Browse files
authored
chore: simplify IsLocked logic (#1177)
1 parent 61e2c14 commit f8678d7

4 files changed

Lines changed: 63 additions & 33 deletions

File tree

builtin/staker/delegations.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,14 @@ func (d *delegations) DisableAutoRenew(delegationID thor.Bytes32) error {
115115
if delegation.Stake.Sign() == 0 {
116116
return errors.New("delegation is not active")
117117
}
118-
if validation.Status == StatusExit {
119-
return errors.New("delegation is not active, withdraw is available")
118+
if delegation.Ended(validation) {
119+
return errors.New("delegation is not active")
120120
}
121121

122122
weight := delegation.Weight()
123123

124124
// the delegation's funds have already been locked, so we need to move them to non-recurring, but still locked
125-
if delegation.IsLocked(validation) {
125+
if delegation.Started(validation) {
126126
// move the delegation's portion of locked to non-recurring.
127127
// this will make the funds available at the end of the current iteration
128128
aggregation.CurrentRecurringVET = big.NewInt(0).Sub(aggregation.CurrentRecurringVET, delegation.Stake)
@@ -141,8 +141,15 @@ func (d *delegations) DisableAutoRenew(delegationID thor.Bytes32) error {
141141
aggregation.PendingRecurringWeight = big.NewInt(0).Sub(aggregation.PendingRecurringWeight, weight)
142142
}
143143

144+
// TODO: In a future PR this won't be possible, so it will be removed. This is backwards compatible according to the unit tests.
145+
// - In future: delegations auto added as auto-renew, then will have to signal an exit to withdraw in the next staking period.
144146
// set the delegation's exit iteration
145-
lastIteration := validation.CurrentIteration() + 1
147+
var lastIteration uint32
148+
if delegation.Started(validation) {
149+
lastIteration = validation.CurrentIteration()
150+
} else {
151+
lastIteration = delegation.FirstIteration
152+
}
146153
delegation.LastIteration = &lastIteration
147154
delegation.AutoRenew = false
148155

@@ -161,8 +168,8 @@ func (d *delegations) EnableAutoRenew(delegationID thor.Bytes32) error {
161168
if delegation.AutoRenew {
162169
return errors.New("delegation is already autoRenew")
163170
}
164-
if validation.Status == StatusExit {
165-
return errors.New("delegation is not active, withdraw is available")
171+
if delegation.Ended(validation) {
172+
return errors.New("delegation is not active")
166173
}
167174
weight := delegation.Weight()
168175

@@ -173,7 +180,7 @@ func (d *delegations) EnableAutoRenew(delegationID thor.Bytes32) error {
173180
return errors.New("validation's next period stake exceeds max stake")
174181
}
175182

176-
if delegation.IsLocked(validation) {
183+
if delegation.Started(validation) {
177184
// move the delegation's portion of non-recurring to locked.
178185
// this means the funds will not be available until the validator is inactive, or the delegation signals an exit
179186
// and completes the current staking period
@@ -205,19 +212,14 @@ func (d *delegations) Withdraw(delegationID thor.Bytes32) (*big.Int, error) {
205212
if err != nil {
206213
return nil, err
207214
}
208-
if delegation.IsLocked(validation) {
215+
started := delegation.Started(validation)
216+
finished := delegation.Ended(validation)
217+
if started && !finished {
209218
return nil, errors.New("delegation is not eligible for withdraw")
210219
}
211220
weight := delegation.Weight()
212221

213-
delegationStarted := delegation.FirstIteration <= validation.CompleteIterations
214-
if delegationStarted && validation.Status != StatusQueued {
215-
// the stake has moved to withdrawable since we checked if the validation is locked above
216-
if aggregation.WithdrawableVET.Cmp(delegation.Stake) < 0 {
217-
return nil, errors.New("not enough withdraw VET")
218-
}
219-
aggregation.WithdrawableVET = big.NewInt(0).Sub(aggregation.WithdrawableVET, delegation.Stake)
220-
} else {
222+
if !started {
221223
if delegation.AutoRenew { // delegation's stake is pending locked
222224
if aggregation.PendingRecurringVET.Cmp(delegation.Stake) < 0 {
223225
return nil, errors.New("not enough pending locked VET")
@@ -239,6 +241,14 @@ func (d *delegations) Withdraw(delegationID thor.Bytes32) (*big.Int, error) {
239241
}
240242
}
241243

244+
if finished {
245+
// the stake has moved to withdrawable since we checked if the validation is locked above
246+
if aggregation.WithdrawableVET.Cmp(delegation.Stake) < 0 {
247+
return nil, errors.New("not enough withdraw VET")
248+
}
249+
aggregation.WithdrawableVET = big.NewInt(0).Sub(aggregation.WithdrawableVET, delegation.Stake)
250+
}
251+
242252
stake := delegation.Stake
243253
delegation.Stake = big.NewInt(0)
244254
// remove the delegation from the mapping after the withdraw

builtin/staker/delegations_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ func Test_IsLocked(t *testing.T) {
5252
CompleteIterations: 2,
5353
}
5454

55-
assert.False(t, d.IsLocked(v), "should not be locked when complete iterations is equal to last iteration")
55+
assert.True(t, d.Started(v), "should not be locked when complete iterations is equal to last iteration")
56+
assert.True(t, d.Ended(v), "should be locked when first is less than current and last is equal to current")
5657
})
5758

5859
t.Run("Incomplete Staking Periods", func(t *testing.T) {
@@ -69,7 +70,8 @@ func Test_IsLocked(t *testing.T) {
6970
CompleteIterations: 3,
7071
}
7172

72-
assert.True(t, d.IsLocked(v), "should be locked when first is less than current and last is greater")
73+
assert.True(t, d.Started(v), "should be started when complete iterations is greater than first iteration")
74+
assert.False(t, d.Ended(v), "should not be locked when first is less than current and last is greater than current")
7375
})
7476

7577
t.Run("Delegation Not Started", func(t *testing.T) {
@@ -86,7 +88,8 @@ func Test_IsLocked(t *testing.T) {
8688
CompleteIterations: 3,
8789
}
8890

89-
assert.False(t, d.IsLocked(v), "should not be locked if delegation has not started yet")
91+
assert.False(t, d.Started(v), "should not be started when complete iterations is less than first iteration")
92+
assert.False(t, d.Ended(v), "should not be locked when first is greater than current and last is greater than current")
9093
})
9194
t.Run("Staker is Queued", func(t *testing.T) {
9295
d := &Delegation{
@@ -101,7 +104,8 @@ func Test_IsLocked(t *testing.T) {
101104
CompleteIterations: 0,
102105
}
103106

104-
assert.False(t, d.IsLocked(v), "should not be locked when validation status is queued")
107+
assert.False(t, d.Started(v), "should not be started when validation status is queued")
108+
assert.False(t, d.Ended(v), "should not be locked when validation status is queued")
105109
})
106110

107111
t.Run("Exit block not defined", func(t *testing.T) {
@@ -117,7 +121,8 @@ func Test_IsLocked(t *testing.T) {
117121
CompleteIterations: 0,
118122
}
119123

120-
assert.True(t, d.IsLocked(v), "should be locked when last iteration is nil and first equals current")
124+
assert.True(t, d.Started(v), "should be started when first iteration is less than current")
125+
assert.False(t, d.Ended(v), "should not be locked when last iteration is nil and first equals current")
121126
})
122127
}
123128

@@ -662,7 +667,7 @@ func Test_Delegations_EnableAutoRenew_MatchStakeReached(t *testing.T) {
662667
assert.NoError(t, err)
663668
validation, err := staker.Get(validator.ID)
664669
assert.NoError(t, err)
665-
assert.False(t, delegation1.IsLocked(validation))
670+
assert.False(t, delegation1.Started(validation))
666671

667672
// Delegation should become active
668673
_, _, err = staker.Housekeep(validator.Period)
@@ -671,7 +676,7 @@ func Test_Delegations_EnableAutoRenew_MatchStakeReached(t *testing.T) {
671676
assert.NoError(t, err)
672677
validation, err = staker.Get(validator.ID)
673678
assert.NoError(t, err)
674-
assert.True(t, delegation1.IsLocked(validation))
679+
assert.True(t, delegation1.Started(validation))
675680

676681
// Enable auto renew for delegation. Should be possible since the max stake won't be reached.
677682
assert.NoError(t, staker.UpdateDelegationAutoRenew(delegationID, true))

builtin/staker/types.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,23 +131,37 @@ func (d *Delegation) Weight() *big.Int {
131131
return weight
132132
}
133133

134-
// IsLocked returns whether the delegator is locked for the current staking period.
135-
func (d *Delegation) IsLocked(validation *Validation) bool {
134+
// Started returns whether the delegation became locked
135+
func (d *Delegation) Started(validation *Validation) bool {
136136
if d.IsEmpty() {
137137
return false
138138
}
139-
// validation is not active, so the delegator is not locked
140-
if validation.Status != StatusActive {
139+
if validation.Status == StatusQueued {
140+
return false // Delegation cannot start if the validation is not active
141+
}
142+
currentStakingPeriod := validation.CurrentIteration()
143+
return currentStakingPeriod >= d.FirstIteration
144+
}
145+
146+
// Ended returns whether the delegation has ended
147+
// It returns true if:
148+
// - the delegation's exit iteration is less than the current staking period
149+
// - OR if the validation is in exit status and the delegation has started
150+
func (d *Delegation) Ended(validation *Validation) bool {
151+
if d.IsEmpty() {
141152
return false
142153
}
143-
current := validation.CurrentIteration()
144-
if d.LastIteration == nil {
145-
return current >= d.FirstIteration
154+
if validation.Status == StatusQueued {
155+
return false // Delegation cannot end if the validation is not active
156+
}
157+
if validation.Status == StatusExit && d.Started(validation) {
158+
return true // Delegation is ended if the validation is in exit status
146159
}
147-
if current < d.FirstIteration {
160+
currentStakingPeriod := validation.CurrentIteration()
161+
if d.LastIteration == nil {
148162
return false
149163
}
150-
return current == d.FirstIteration || current < *d.LastIteration
164+
return *d.LastIteration < currentStakingPeriod
151165
}
152166

153167
// Aggregation represents the total amount of VET locked for a given validation's delegations.

builtin/staker_native.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ func init() {
311311
if delegation.LastIteration != nil {
312312
lastPeriod = *delegation.LastIteration
313313
}
314-
return []any{delegation.ValidationID, delegation.Stake, delegation.FirstIteration, lastPeriod, delegation.Multiplier, delegation.AutoRenew, delegation.IsLocked(validator), ""}
314+
locked := delegation.Started(validator) && !delegation.Ended(validator)
315+
return []any{delegation.ValidationID, delegation.Stake, delegation.FirstIteration, lastPeriod, delegation.Multiplier, delegation.AutoRenew, locked, ""}
315316
}},
316317
{"native_getRewards", func(env *xenv.Environment) []any {
317318
var args struct {

0 commit comments

Comments
 (0)