Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Fix valset pref unbonding bugs #5967

Merged
merged 35 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fe7290f
Add helper to get existing delegations along with valset pref
ValarDragon Aug 6, 2023
fcdc961
Lay out more of the logic nuance
ValarDragon Aug 6, 2023
58792cc
Add pseudo-code needed
ValarDragon Aug 6, 2023
2f8dea8
algorithm v1
stackman27 Aug 7, 2023
f4c0dab
added algorithm docs
stackman27 Aug 7, 2023
144f0d1
fixed all test
stackman27 Aug 8, 2023
41cd2f6
removed unwanted files
stackman27 Aug 8, 2023
7403e06
remove more code
stackman27 Aug 8, 2023
fbd4ba4
added more tests
stackman27 Aug 8, 2023
2f5caca
update changelog
devbot-wizard Aug 8, 2023
d3cec49
Merge branch 'main' into dev/valset-pref-unbond-issues
stackman27 Aug 8, 2023
90e9fb2
added test and addressed feedback
stackman27 Aug 8, 2023
b317a0e
Update x/valset-pref/validator_set.go
czarcas7ic Aug 9, 2023
b1ece1b
Update x/valset-pref/validator_set.go
czarcas7ic Aug 9, 2023
53a14d9
Update x/valset-pref/README.md
czarcas7ic Aug 9, 2023
5d54956
Merge branch 'main' into dev/valset-pref-unbond-issues
czarcas7ic Aug 11, 2023
11bc176
Minor cleanup
ValarDragon Aug 13, 2023
3799111
re-use validator gets
ValarDragon Aug 13, 2023
dcfc806
Refactor
ValarDragon Aug 14, 2023
d9891dd
Highlight bug
ValarDragon Aug 14, 2023
5ac4023
fixed the issue
stackman27 Aug 22, 2023
25cdc5f
added comments
stackman27 Aug 22, 2023
20f7c29
Merge branch 'main' into dev/valset-pref-unbond-issues
stackman27 Aug 22, 2023
e27f258
update changelog
devbot-wizard Aug 8, 2023
54b4dec
fixed the issue
stackman27 Aug 22, 2023
77a9655
fixed go test
stackman27 Aug 22, 2023
324a281
fixed test
stackman27 Aug 22, 2023
365b721
Merge branch 'main' into dev/valset-pref-unbond-issues
p0mvn Sep 27, 2023
31e9008
lint
p0mvn Sep 28, 2023
1c48875
Merge branch 'main' into dev/valset-pref-unbond-issues
p0mvn Oct 3, 2023
9e98292
Finish ValSet Pref Unbonding (#6630)
mattverse Oct 5, 2023
eedd243
Merge branch 'main' into dev/valset-pref-unbond-issues
czarcas7ic Oct 5, 2023
ce280e8
feat: unbond with rebalanced val set weights (#6685)
czarcas7ic Oct 13, 2023
30af740
Merge branch 'main' into dev/valset-pref-unbond-issues
czarcas7ic Oct 13, 2023
246d95e
Update x/valset-pref/validator_set.go
ValarDragon Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#5890](https://github.com/osmosis-labs/osmosis/pull/5890) feat: CreateCLPool & LinkCFMMtoCL pool into one gov-prop
* [#5959](https://github.com/osmosis-labs/osmosis/pull/5959) allow testing with different chain-id's in E2E testing
* [#5964](https://github.com/osmosis-labs/osmosis/pull/5964) fix e2e test concurrency bugs
* [#5948](https://github.com/osmosis-labs/osmosis/pull/5948) Parameterizing Pool Type Information in Protorev
* [#5967](https://github.com/osmosis-labs/osmosis/pull/5967) fix ValSet undelegate API out of sync with existing staking
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
* [#6001](https://github.com/osmosis-labs/osmosis/pull/6001) feat: improve set-env CLI cmd
* [#6012](https://github.com/osmosis-labs/osmosis/pull/6012) chore: add autocomplete to makefile

Expand Down
57 changes: 48 additions & 9 deletions x/valset-pref/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,54 @@ How does this module work?
- If the delegator has not set a validator-set preference list then the validator set, then it defaults to their current validator set.
- If a user has no preference list and has not staked, then these messages / queries return errors.

## Calculations

Staking Calculation

- The user provides an amount to delegate and our `MsgDelegateToValidatorSet` divides the amount based on validator weight distribution.
For example: Stake 100osmo with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}
our delegate logic will attempt to delegate (100 * 0.5) 50osmo for ValA , (100 * 0.3) 30osmo from ValB and (100 * 0.2) 20osmo from ValC.

UnStaking Calculation
## Logic

### Delegate to validator set

This is pretty straight-forward, theres not really any edge cases here.

The user provides an amount to delegate and our `MsgDelegateToValidatorSet` divides the amount based on validator weight distribution.

For example: Stake 100osmo with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}
our delegate logic will attempt to delegate (100 * 0.5) 50osmo for ValA , (100 * 0.3) 30osmo from ValB and (100 * 0.2) 20osmo from ValC.

### Undelegate from validator set

We can imagine describing undelegate from validator set in two cases:
- Users existing delegation distribution matches their validator-set preference distribution.
- Users existing delegation distribution does not match their validator-set preference distribution.

Algorithm for undelegation;
unbond as true to valset ratios as possible. Undelegation should be possible.
Idea of what we should be doing for Undelegate(valset, amount):
1. Calculate the amount to undelegate from each validator under full valset usage
2. If all amounts are less than amount staked to validator, undelegate all
3. If any amount is greater than amount staked to validator (S,V),
fully unstake S from that validator. Recursively proceed with undelegating the
remaining amount from the remaining validators.
Undelegate(valset - V, amount - S)

The above algorithm would take O(V^2) worst case, so we instead do something better
to be O(V).

1. Calculate the amount to undelegate from each validator under full valset usage
2. For each validator, compute V.ratio = undelegate_amount / amount_staked_to_val
3. Sort validators by V_ratio descending If V_ratio <= 1, undelegate taret amount from each validator. (happy path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
3. Sort validators by V_ratio descending If V_ratio <= 1, undelegate taret amount from each validator. (happy path)
3. Sort validators by V_ratio descending.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dev was in favor of keeping the algorithm as verbose as possible, so i didn't make any change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this removal suggestion is due to duplicate lines right below!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right i see, i will remove it :))

4. If V_ratio <= 1, undelegate target amount from each validator. (happy path)
5. Set target_ratio = 1, amount_remaining_to_unbond = amount
6. While greatest V_ratio > target_ratio:
- Fully undelegate validator with greatest V_ratio. (Amount S)
- remove validator from list
- recalculate target_ratio = target_ratio * (1 - removed_V.target_percent)
- this works, because if you recalculated target ratio scaled to 1
every val's ratio would just differ by the constant factor of 1 / (1 - removed_V.target_percent)
doing that would take O(V), hence we change the target.
7. Normal undelegate the remainder.


#### Case 1: Users existing delegation distribution matches their validator-set preference distribution

{Old docs below}

- The user provides an amount to undelegate and our `MsgUnDelegateToValidatorSet` divides the amount based on validator weight distribution.
- Here, the user can either undelegate the entire amount or partial amount
Expand Down
4 changes: 2 additions & 2 deletions x/valset-pref/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var _ types.MsgServer = msgServer{}
func (server msgServer) SetValidatorSetPreference(goCtx context.Context, msg *types.MsgSetValidatorSetPreference) (*types.MsgSetValidatorSetPreferenceResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

preferences, err := server.keeper.SetValidatorSetPreference(ctx, msg.Delegator, msg.Preferences)
preferences, err := server.keeper.DeriveValidatorSetPreference(ctx, msg.Delegator, msg.Preferences)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -75,7 +75,7 @@ func (server msgServer) RedelegateValidatorSet(goCtx context.Context, msg *types
}

// Message 1: override the validator set preference set entry
newPreferences, err := server.keeper.SetValidatorSetPreference(ctx, msg.Delegator, msg.Preferences)
newPreferences, err := server.keeper.DeriveValidatorSetPreference(ctx, msg.Delegator, msg.Preferences)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions x/valset-pref/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ func (s *KeeperTestSuite) TestUnDelegateFromValidatorSet() {
delegator: sdk.AccAddress([]byte("addr6---------------")),
coinToStake: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100_000_000)),
coinToUnStake: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(87_461_351)),
expectedShares: []sdk.Dec{sdk.NewDec(2_507_730), sdk.NewDec(4_137_755), sdk.NewDec(1_504_638), sdk.NewDec(4_388_526)}, // validatorDelegatedShares - (weight * coinToUnstake), for ex: 20_000_000 - (0.2 * 87_461_351)
expectedShares: []sdk.Dec{sdk.NewDec(2_507_730), sdk.NewDec(4_137_753), sdk.NewDec(1_504_638), sdk.NewDec(4_388_528)}, // validatorDelegatedShares - (weight * coinToUnstake), for ex: 20_000_000 - (0.2 * 87_461_351)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason these numbers changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored the test to check for undelegated tokens:https://github.com/osmosis-labs/osmosis/pull/5967/files#diff-e8d94c443b26e1cda354d150b30ce5542344099e0bbbb8e84e507d9df791daf7R309

however, the reason why this was changed was because we sort the tokens and take out the remaining amount from the last validator in the new algorithm

setValSet: true,
expectPass: true,
},
Expand All @@ -376,7 +376,7 @@ func (s *KeeperTestSuite) TestUnDelegateFromValidatorSet() {
delegator: sdk.AccAddress([]byte("addr7---------------")),
coinToStake: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10_000_000)),
coinToUnStake: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1234)),
expectedShares: []sdk.Dec{sdk.NewDec(1_999_754), sdk.NewDec(3_299_593), sdk.NewDec(1_199_852), sdk.NewDec(3_499_567)}, // validatorDelegatedShares - (weight * coinToUnstake),
expectedShares: []sdk.Dec{sdk.NewDec(1_999_752), sdk.NewDec(3_299_593), sdk.NewDec(1_199_852), sdk.NewDec(3_499_569)}, // validatorDelegatedShares - (weight * coinToUnstake),
setValSet: true,
expectPass: true,
},
Expand Down
158 changes: 114 additions & 44 deletions x/valset-pref/validator_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ type valSet struct {
Amount sdk.Dec
}

type ValRatio struct {
ValAddr sdk.ValAddress
Weight sdk.Dec
DelegatedAmt sdk.Int
UndelegateAmt sdk.Int
VRatio sdk.Dec
}

// SetValidatorSetPreferences sets a new valset position for a delegator in modules state.
func (k Keeper) SetValidatorSetPreferences(ctx sdk.Context, delegator string, validators types.ValidatorSetPreferences) {
store := ctx.KVStore(k.storeKey)
Expand All @@ -44,9 +52,11 @@ func (k Keeper) GetValidatorSetPreference(ctx sdk.Context, delegator string) (ty
return valsetPref, true
}

// SetValidatorSetPreference creates or updates delegators validator set.
// DeriveValidatorSetPreference derives given validator set.
// It validates the list and formats the inputs such as rounding.
// Errors when the given preference is the same as the existing preference in state.
func (k Keeper) SetValidatorSetPreference(ctx sdk.Context, delegator string, preferences []types.ValidatorPreference) (types.ValidatorSetPreferences, error) {
// NOTE: this function does not add valset to the state
func (k Keeper) DeriveValidatorSetPreference(ctx sdk.Context, delegator string, preferences []types.ValidatorPreference) (types.ValidatorSetPreferences, error) {
existingValSet, found := k.GetValidatorSetPreference(ctx, delegator)
if found {
// check if the new preferences is the same as the existing preferences
Expand Down Expand Up @@ -99,10 +109,9 @@ func (k Keeper) DelegateToValidatorSet(ctx sdk.Context, delegatorAddr string, co
tokenAmt = coin.Amount.Sub(totalDelAmt).ToDec().TruncateInt()
} else {
// tokenAmt takes the amount to delegate, calculated by {val_distribution_weight * tokenAmt}
tokenAmt = val.Weight.Mul(coin.Amount.ToDec()).TruncateInt()
tokenAmt = val.Weight.MulInt(coin.Amount).TruncateInt()
totalDelAmt = totalDelAmt.Add(tokenAmt)
}

// TODO: What happens here if validator unbonding
// Delegate the unbonded tokens
_, err = k.stakingKeeper.Delegate(ctx, delegator, tokenAmt, stakingtypes.Unbonded, validator, true)
Expand All @@ -119,72 +128,133 @@ func (k Keeper) DelegateToValidatorSet(ctx sdk.Context, delegatorAddr string, co
// For ex: userA has staked 10tokens with weight {Val->0.5, ValB->0.3, ValC->0.2}
// undelegate 6osmo with validator-set {ValA -> 0.5, ValB -> 0.3, ValC -> 0.2}
// our undelegate logic would attempt to undelegate 3osmo from A, 1.8osmo from B, 1.2osmo from C
// nolint: staticcheck
// Rounding logic ensures we do not undelegate more than the user has staked with the validator set.
// NOTE: check readme.md for more verbose description of the algorithm.
func (k Keeper) UndelegateFromValidatorSet(ctx sdk.Context, delegatorAddr string, coin sdk.Coin) error {
// get the existingValSet if it exists, if not check existingStakingPosition and return it
existingSet, err := k.GetDelegationPreferences(ctx, delegatorAddr)
if err != nil {
return fmt.Errorf("user %s doesn't have validator set", delegatorAddr)
}

delegator, err := sdk.AccAddressFromBech32(delegatorAddr)
if err != nil {
return err
}

// the total amount the user wants to undelegate
tokenAmt := sdk.NewDec(coin.Amount.Int64())

err = k.CheckUndelegateTotalAmount(tokenAmt, existingSet.Preferences)
if err != nil {
return err
}

// totalDelAmt is the amount that keeps running track of the amount of tokens undelegated
totalUnDelAmt := sdk.NewInt(0)
amountToUnDelegate := sdk.NewInt(0)
delegator := sdk.MustAccAddressFromBech32(delegatorAddr)
// total amount the user wants to undelegate
tokenAmtToUndelegate := coin.Amount.ToDec()
// total amount user has delegated
totalDelegatedAmt := sdk.ZeroDec()

for i, val := range existingSet.Preferences {
// Step 1-2, compute the total amount delegated and the amount to undelegate for each validator
// under valset-ratios.
var valSetRatio []ValRatio
validators := map[string]stakingtypes.Validator{}
for _, val := range existingSet.Preferences {
amountToUnDelegate := val.Weight.Mul(tokenAmtToUndelegate).TruncateInt()
valAddr, validator, err := k.getValAddrAndVal(ctx, val.ValOperAddress)
if err != nil {
return err
}
validators[valAddr.String()] = validator

// in the last valset iteration we dont calculate it from shares using decimals and trucation,
// we use whats remaining to get more accurate value
if len(existingSet.Preferences)-1 == i {
amountToUnDelegate = coin.Amount.Sub(totalUnDelAmt).ToDec().TruncateInt()
} else {
// Calculate the amount to undelegate based on the existing weights
amountToUnDelegate = val.Weight.Mul(tokenAmt).TruncateInt()
totalUnDelAmt = totalUnDelAmt.Add(amountToUnDelegate)
delegation, found := k.stakingKeeper.GetDelegation(ctx, delegator, valAddr)
if !found {
return fmt.Errorf("No delegation found for thie delegator %s to this validator %s\n", delegator, valAddr)
}

sharesAmt, err := validator.SharesFromTokens(amountToUnDelegate)
if err != nil {
return err
}

_, err = k.stakingKeeper.Undelegate(ctx, delegator, valAddr, sharesAmt) // this has to be shares amount
valShares := sharesAmt.Quo(delegation.Shares)
tokenAmt := validator.TokensFromShares(delegation.Shares)
totalDelegatedAmt = totalDelegatedAmt.Add(tokenAmt)

valSetRatio = append(valSetRatio, ValRatio{
ValAddr: valAddr,
UndelegateAmt: amountToUnDelegate,
VRatio: valShares,
DelegatedAmt: delegation.Shares.TruncateInt(),
Weight: val.Weight,
})
}

if tokenAmtToUndelegate.GT(totalDelegatedAmt) {
return fmt.Errorf("Total tokenAmountToUndelegate more than delegated amount have %s got %s\n", totalDelegatedAmt, tokenAmtToUndelegate)
}

// Step 3
sort.Slice(valSetRatio, func(i, j int) bool {
return valSetRatio[i].VRatio.GT(valSetRatio[j].VRatio)
})

// Step 4
totalUnDelAmt, amountToUnDelegate := sdk.NewInt(0), sdk.NewInt(0)
if valSetRatio[0].VRatio.LTE(sdk.OneDec()) {
fmt.Println("All validators can undelegate normally, falling to happy path exit early")
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
for index, val := range valSetRatio {
validator := validators[val.ValAddr.String()]

// in the last valset iteration we dont calculate it from shares using decimals and trucation,
// we use whats remaining to get more accurate value
if len(existingSet.Preferences)-1 == index {
amountToUnDelegate = coin.Amount.Sub(totalUnDelAmt).ToDec().TruncateInt()
} else {
// Calculate the amount to undelegate based on the existing weights
amountToUnDelegate = val.UndelegateAmt
totalUnDelAmt = totalUnDelAmt.Add(amountToUnDelegate)
}

sharesAmt, err := validator.SharesFromTokens(amountToUnDelegate)
if err != nil {
return err
}

_, err = k.stakingKeeper.Undelegate(ctx, delegator, val.ValAddr, sharesAmt) // this has to be shares amount
if err != nil {
return err
}
}

return nil
}

// Step 5
// `targetRatio`: This is a threshold value that is used to decide how to unbond tokens from validators.
// It starts as 1 and is recalculated each time a validator is fully unbonded and removed from the unbonding process.
// By reducing the target ratio using the ratio of the removed validator, we adjust the proportions we are aiming for with the remaining validators.
targetRatio := sdk.OneDec()
amountRemaining := coin.Amount

// Step 6
for len(valSetRatio) > 0 && valSetRatio[0].VRatio.GT(targetRatio) {
fmt.Printf("Undelegating fully from validator %s %s\n", valSetRatio[0].ValAddr.String(), valSetRatio[0].DelegatedAmt)
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
_, err = k.stakingKeeper.Undelegate(ctx, delegator, valSetRatio[0].ValAddr, valSetRatio[0].DelegatedAmt.ToDec()) // this has to be shares amount
if err != nil {
return err
}
amountRemaining = amountRemaining.Sub(valSetRatio[0].DelegatedAmt)
targetRatio = targetRatio.Mul(sdk.OneDec().Sub(valSetRatio[0].Weight))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't quite follow exactly how we do re-calculation and use target ratio here , can you give me some examples with numbers? 👀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup so if we have 2 validators and have userA who has staked 20token

ValA = 15token, weight: 0.5
ValB = 5Token, weight: 0.5

Run undelegate 20token

ValA VRatio =  0.5 * 20.0 = 10, VRatio = 10/15 = 0.6667
ValB VRatio =  0.5 * 20.0 = 10, VRatio = 10/5 = 2.0

determine the maxVRatio = 2.0, we set targetVRatio to 1, since we want to normalize all the weights 

For validator B, VRatio 2.0 > 1.0. So, 
- perform full undelegation, 
- reduce amount remaining (20 - stake of B, which is 5) = 15, 
- adjust target ratio (1.0 * (1 - RatioInValset of B, which is 0.5)) = 0.5, 
- Remove B from validators list.
- do this until valSetRatio is LTE target ratio

Now only Validator A is left and amount remaining is 15, so we undelegate the rest from ValA

this is a full flow of how the algorithm works, please lmk if that makes sense. Happy to explain more :))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this is awesome, thanks for the example, would you mind adding this to readme? It personally helped me understand what's going on right away with this example

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is valid, I don't think it is currently possible to have a vratio greater than 1. Would love to see a test case that touches steps 5-7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with the described calculation in this example is the Vratio. In this example we take 10/15 and 10/5. This would be possible if we used GetValSetPreferences method in the withdraw logic, but we instead use GetValSetPreferencesWithDelegations. This transforms the weights based on the amount actually delegated to the validators. So in that case, the numerator could never be larger than the denominator.

valSetRatio = valSetRatio[1:]
}
return nil
}

// CheckUndelegateTotalAmount checks if the tokenAmount equals the total amount calculated from valset weights.
func (k Keeper) CheckUndelegateTotalAmount(tokenAmt sdk.Dec, existingSet []types.ValidatorPreference) error {
totalAmountFromWeights := sdk.NewDec(0)
for _, val := range existingSet {
totalAmountFromWeights = totalAmountFromWeights.Add(val.Weight.Mul(tokenAmt))
}
// Step 7
fmt.Println("Undelegating the remaining amount.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: i'm a little worried about this part of the algorithm. would appreciate a thorough review here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which part in specific are you worried about?

stackman27 marked this conversation as resolved.
Show resolved Hide resolved
for _, val := range valSetRatio {
// TestUndelegateFromValSetErrorCase1 specifically checks this logic
fmt.Printf("Undelegate %s from validator %s\n", val.UndelegateAmt, val.ValAddr.String())
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
_, validator, err := k.getValAddrAndVal(ctx, val.ValAddr.String())
if err != nil {
return err
}

totalAmountFromWeights = totalAmountFromWeights.RoundInt().ToDec()
tokenAmt = tokenAmt.RoundInt().ToDec()
sharesAmt, err := validator.SharesFromTokens(val.UndelegateAmt)
if err != nil {
return err
}

if !totalAmountFromWeights.Equal(tokenAmt) {
return fmt.Errorf("The undelegate total do not add up with the amount calculated from weights expected %s got %s", tokenAmt, totalAmountFromWeights)
_, err = k.stakingKeeper.Undelegate(ctx, delegator, val.ValAddr, sharesAmt) // this has to be shares amount
if err != nil {
return err
}
}

return nil
Expand Down
Loading