/
undelegate_host.go
137 lines (114 loc) · 5.04 KB
/
undelegate_host.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package keeper
import (
"fmt"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/gogoproto/proto"
"github.com/Stride-Labs/stride/v16/utils"
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)
const (
MaxNumTokensUnbondableStr = "2500000000000000000000000" // 2,500,000e18
EvmosHostZoneChainId = "evmos_9001-2"
)
// Submits undelegation ICA message for Evmos
// The total unbond amount is input, capped at MaxNumTokensUnbondable.
func (k Keeper) UndelegateHostEvmos(ctx sdk.Context, totalUnbondAmount math.Int) error {
// if the total unbond amount is greater than the max, exit
maxNumTokensUnbondable, ok := math.NewIntFromString(MaxNumTokensUnbondableStr)
if !ok {
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "unable to parse maxNumTokensUnbondable %s", maxNumTokensUnbondable)
}
if totalUnbondAmount.GT(maxNumTokensUnbondable) {
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "total unbond amount %v is greater than maxNumTokensUnbondable %v",
totalUnbondAmount, maxNumTokensUnbondable)
}
// Get the host zone
evmosHost, found := k.GetHostZone(ctx, EvmosHostZoneChainId)
if !found {
return errorsmod.Wrapf(types.ErrHostZoneNotFound, "host zone %s not found", EvmosHostZoneChainId)
}
k.Logger(ctx).Info(utils.LogWithHostZone(evmosHost.ChainId,
"Total unbonded amount: %v%s", totalUnbondAmount, evmosHost.HostDenom))
// If there's nothing to unbond, return and move on to the next host zone
if totalUnbondAmount.IsZero() {
return nil
}
k.Logger(ctx).Info("Preparing MsgUndelegates from the delegation account to each validator on Evmos")
// Confirm the delegation account was registered
if evmosHost.DelegationIcaAddress == "" {
return errorsmod.Wrapf(types.ErrICAAccountNotFound, "no delegation account found for %s", evmosHost.ChainId)
}
// Determine the ideal balanced delegation for each validator after the unbonding
// (as if we were to unbond and then rebalance)
// This will serve as the starting point for determining how much to unbond each validator
delegationAfterUnbonding := evmosHost.TotalDelegations.Sub(totalUnbondAmount)
balancedDelegationsAfterUnbonding, err := k.GetTargetValAmtsForHostZone(ctx, evmosHost, delegationAfterUnbonding)
if err != nil {
return errorsmod.Wrapf(err, "unable to get target val amounts for host zone %s", evmosHost.ChainId)
}
// Determine the unbond capacity for each validator
// Each validator can only unbond up to the difference between their current delegation and their balanced delegation
// The validator's current delegation will be above their balanced delegation if they've received LSM Liquid Stakes
// (which is only rebalanced once per unbonding period)
validatorUnbondCapacity := k.GetValidatorUnbondCapacity(ctx, evmosHost.Validators, balancedDelegationsAfterUnbonding)
if len(validatorUnbondCapacity) == 0 {
return fmt.Errorf("there are no validators on %s with sufficient unbond capacity", evmosHost.ChainId)
}
// Sort the unbonding capacity by priority
// Priority is determined by checking the how proportionally unbalanced each validator is
// Zero weight validators will come first in the list
prioritizedUnbondCapacity, err := SortUnbondingCapacityByPriority(validatorUnbondCapacity)
if err != nil {
return err
}
// Get the undelegation ICA messages and split delegations for the callback
msgs, unbondings, err := k.GetUnbondingICAMessages(evmosHost, totalUnbondAmount, prioritizedUnbondCapacity)
if err != nil {
return err
}
// Shouldn't be possible, but if all the validator's had a target unbonding of zero, do not send an ICA
if len(msgs) == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Target unbonded amount was 0 for each validator")
}
// Send the messages in batches so the gas limit isn't exceedeed
for start := 0; start < len(msgs); start += UndelegateICABatchSize {
end := start + UndelegateICABatchSize
if end > len(msgs) {
end = len(msgs)
}
msgsBatch := msgs[start:end]
unbondingsBatch := unbondings[start:end]
// Store the callback data
undelegateHostCallback := types.UndelegateHostCallback{
Amt: totalUnbondAmount,
SplitDelegations: unbondingsBatch,
}
callbackArgsBz, err := proto.Marshal(&undelegateHostCallback)
if err != nil {
return errorsmod.Wrap(err, "unable to marshal undelegate callback args")
}
// Submit the undelegation ICA
if _, err := k.SubmitTxsDayEpoch(
ctx,
evmosHost.ConnectionId,
msgsBatch,
types.ICAAccountType_DELEGATION,
ICACallbackID_UndelegateHost,
callbackArgsBz,
); err != nil {
return errorsmod.Wrapf(err, "unable to submit unbonding ICA for %s", evmosHost.ChainId)
}
// flag the delegation change in progress on each validator
for _, unbonding := range unbondingsBatch {
if err := k.IncrementValidatorDelegationChangesInProgress(&evmosHost, unbonding.Validator); err != nil {
return err
}
}
k.SetHostZone(ctx, evmosHost)
}
EmitUndelegationEvent(ctx, evmosHost, totalUnbondAmount)
return nil
}