From 2385e93a5c34776fd7ff49009d2cded9e4c21fea Mon Sep 17 00:00:00 2001 From: MSalopek Date: Mon, 11 Nov 2024 13:50:57 +0100 Subject: [PATCH 1/5] fix!: validate shares amount during TokenizeShare --- x/staking/keeper/msg_server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 512cfd1ec167..ab9739e93bc5 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -830,6 +830,11 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS return nil, err } + // sanity check to avoid creating a tokenized share record with zero shares + if shares.IsZero() { + return nil, errorsmod.Wrap(types.ErrInsufficientShares, "cannot tokenize zero shares") + } + // Check that the delegator has no ongoing redelegations to the validator found, err := k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr) if err != nil { From a303122462b51928fda0095b0450ccb89030ae62 Mon Sep 17 00:00:00 2001 From: MSalopek Date: Mon, 11 Nov 2024 14:20:01 +0100 Subject: [PATCH 2/5] nits: add more err checks for store operations --- x/staking/keeper/liquid_stake.go | 60 ++++++++++++++++++----- x/staking/keeper/msg_server.go | 26 ++++++++-- x/staking/keeper/tokenize_share_record.go | 40 ++++++++++++--- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/x/staking/keeper/liquid_stake.go b/x/staking/keeper/liquid_stake.go index 91dde61a9e28..9c3292c76c3f 100644 --- a/x/staking/keeper/liquid_stake.go +++ b/x/staking/keeper/liquid_stake.go @@ -22,7 +22,10 @@ func (k Keeper) SetTotalLiquidStakedTokens(ctx context.Context, tokens math.Int) panic(err) } - store.Set(types.TotalLiquidStakedTokensKey, tokensBz) + err = store.Set(types.TotalLiquidStakedTokensKey, tokensBz) + if err != nil { + panic(err) + } } // GetTotalLiquidStakedTokens returns the total outstanding tokens owned by a liquid staking provider @@ -204,7 +207,10 @@ func (k Keeper) SafelyIncreaseValidatorLiquidShares(ctx context.Context, valAddr // Increment the validator's liquid shares validator.LiquidShares = validator.LiquidShares.Add(shares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return types.Validator{}, err + } return validator, nil } @@ -221,7 +227,10 @@ func (k Keeper) DecreaseValidatorLiquidShares(ctx context.Context, valAddress sd } validator.LiquidShares = validator.LiquidShares.Sub(shares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return types.Validator{}, err + } return validator, nil } @@ -235,7 +244,10 @@ func (k Keeper) IncreaseValidatorBondShares(ctx context.Context, valAddress sdk. } validator.ValidatorBondShares = validator.ValidatorBondShares.Add(shares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return err + } return nil } @@ -264,7 +276,10 @@ func (k Keeper) SafelyDecreaseValidatorBond(ctx context.Context, valAddress sdk. // Decrement the validator's self bond validator.ValidatorBondShares = validator.ValidatorBondShares.Sub(shares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return err + } return nil } @@ -276,21 +291,30 @@ func (k Keeper) SafelyDecreaseValidatorBond(ctx context.Context, valAddress sdk. func (k Keeper) AddTokenizeSharesLock(ctx context.Context, address sdk.AccAddress) { store := k.storeService.OpenKVStore(ctx) key := types.GetTokenizeSharesLockKey(address) - store.Set(key, sdk.FormatTimeBytes(time.Time{})) + err := store.Set(key, sdk.FormatTimeBytes(time.Time{})) + if err != nil { + panic(err) + } } // Removes the tokenize share lock for an account to enable tokenizing shares func (k Keeper) RemoveTokenizeSharesLock(ctx context.Context, address sdk.AccAddress) { store := k.storeService.OpenKVStore(ctx) key := types.GetTokenizeSharesLockKey(address) - store.Delete(key) + err := store.Delete(key) + if err != nil { + panic(err) + } } // Updates the timestamp associated with a lock to the time at which the lock expires func (k Keeper) SetTokenizeSharesUnlockTime(ctx context.Context, address sdk.AccAddress, completionTime time.Time) { store := k.storeService.OpenKVStore(ctx) key := types.GetTokenizeSharesLockKey(address) - store.Set(key, sdk.FormatTimeBytes(completionTime)) + err := store.Set(key, sdk.FormatTimeBytes(completionTime)) + if err != nil { + panic(err) + } } // Checks if there is currently a tokenize share lock for a given account @@ -356,7 +380,10 @@ func (k Keeper) SetPendingTokenizeShareAuthorizations(ctx context.Context, compl store := k.storeService.OpenKVStore(ctx) timeKey := types.GetTokenizeShareAuthorizationTimeKey(completionTime) bz := k.cdc.MustMarshal(&authorizations) - store.Set(timeKey, bz) + err := store.Set(timeKey, bz) + if err != nil { + panic(err) + } } // Returns a list of addresses pending tokenize share unlocking at the same time @@ -443,7 +470,10 @@ func (k Keeper) RemoveExpiredTokenizeShareLocks(ctx context.Context, blockTime t // delete unlocked addresses keys for _, k := range keys { - store.Delete(k) + err := store.Delete(k) + if err != nil { + panic(err) + } } // remove the lock from each unlocked address @@ -473,7 +503,10 @@ func (k Keeper) RefreshTotalLiquidStaked(ctx context.Context) error { // First reset each validator's liquid shares to 0 for _, validator := range validators { validator.LiquidShares = math.LegacyZeroDec() - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return err + } } delegations, err := k.GetAllDelegations(ctx) @@ -507,7 +540,10 @@ func (k Keeper) RefreshTotalLiquidStaked(ctx context.Context) error { liquidTokens := validator.TokensFromShares(liquidShares).TruncateInt() validator.LiquidShares = validator.LiquidShares.Add(liquidShares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return err + } totalLiquidStakedTokens = totalLiquidStakedTokens.Add(liquidTokens) } diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index ab9739e93bc5..0bdfc83ce408 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -750,7 +750,11 @@ func (k msgServer) UnbondValidator(ctx context.Context, msg *types.MsgUnbondVali return nil, err } - k.jailValidator(ctx, validator) + err = k.jailValidator(ctx, validator) + if err != nil { + return nil, err + } + return &types.MsgUnbondValidatorResponse{}, nil } @@ -876,7 +880,10 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS } if validator.IsBonded() { - k.bondedTokensToNotBonded(ctx, returnAmount) + err = k.bondedTokensToNotBonded(ctx, returnAmount) + if err != nil { + return nil, err + } } // Note: UndelegateCoinsFromModuleToAccount is internally calling TrackUndelegation for vesting account @@ -1017,7 +1024,10 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe } if validator.IsBonded() { - k.bondedTokensToNotBonded(ctx, returnAmount) + err = k.bondedTokensToNotBonded(ctx, returnAmount) + if err != nil { + return nil, err + } } // Note: since delegation object has been changed from unbond call, it gets latest delegation @@ -1213,9 +1223,15 @@ func (k msgServer) ValidatorBond(goCtx context.Context, msg *types.MsgValidatorB if !delegation.ValidatorBond { delegation.ValidatorBond = true - k.SetDelegation(ctx, delegation) + err = k.SetDelegation(ctx, delegation) + if err != nil { + return nil, err + } validator.ValidatorBondShares = validator.ValidatorBondShares.Add(delegation.Shares) - k.SetValidator(ctx, validator) + err = k.SetValidator(ctx, validator) + if err != nil { + return nil, err + } ctx.EventManager().EmitEvent( sdk.NewEvent( diff --git a/x/staking/keeper/tokenize_share_record.go b/x/staking/keeper/tokenize_share_record.go index 37f0103c729e..74f9ac784965 100644 --- a/x/staking/keeper/tokenize_share_record.go +++ b/x/staking/keeper/tokenize_share_record.go @@ -29,7 +29,10 @@ func (k Keeper) GetLastTokenizeShareRecordID(ctx context.Context) uint64 { func (k Keeper) SetLastTokenizeShareRecordID(ctx context.Context, id uint64) { store := k.storeService.OpenKVStore(ctx) - store.Set(types.LastTokenizeShareRecordIDKey, sdk.Uint64ToBigEndian(id)) + err := store.Set(types.LastTokenizeShareRecordIDKey, sdk.Uint64ToBigEndian(id)) + if err != nil { + panic(err) + } } func (k Keeper) GetTokenizeShareRecord(ctx context.Context, id uint64) (tokenizeShareRecord types.TokenizeShareRecord, err error) { @@ -133,9 +136,18 @@ func (k Keeper) DeleteTokenizeShareRecord(ctx context.Context, recordID uint64) } store := k.storeService.OpenKVStore(ctx) - store.Delete(types.GetTokenizeShareRecordByIndexKey(recordID)) - store.Delete(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, recordID)) - store.Delete(types.GetTokenizeShareRecordIDByDenomKey(record.GetShareTokenDenom())) + err = store.Delete(types.GetTokenizeShareRecordByIndexKey(recordID)) + if err != nil { + return err + } + err = store.Delete(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, recordID)) + if err != nil { + return err + } + err = store.Delete(types.GetTokenizeShareRecordIDByDenomKey(record.GetShareTokenDenom())) + if err != nil { + return err + } return nil } @@ -148,24 +160,36 @@ func (k Keeper) setTokenizeShareRecord(ctx context.Context, tokenizeShareRecord store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&tokenizeShareRecord) - store.Set(types.GetTokenizeShareRecordByIndexKey(tokenizeShareRecord.Id), bz) + err := store.Set(types.GetTokenizeShareRecordByIndexKey(tokenizeShareRecord.Id), bz) + if err != nil { + panic(err) + } } func (k Keeper) setTokenizeShareRecordWithOwner(ctx context.Context, owner sdk.AccAddress, id uint64) { store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&gogotypes.UInt64Value{Value: id}) - store.Set(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, id), bz) + err := store.Set(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, id), bz) + if err != nil { + panic(err) + } } func (k Keeper) deleteTokenizeShareRecordWithOwner(ctx context.Context, owner sdk.AccAddress, id uint64) { store := k.storeService.OpenKVStore(ctx) - store.Delete(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, id)) + err := store.Delete(types.GetTokenizeShareRecordIDByOwnerAndIDKey(owner, id)) + if err != nil { + panic(err) + } } func (k Keeper) setTokenizeShareRecordWithDenom(ctx context.Context, denom string, id uint64) { store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&gogotypes.UInt64Value{Value: id}) - store.Set(types.GetTokenizeShareRecordIDByDenomKey(denom), bz) + err := store.Set(types.GetTokenizeShareRecordIDByDenomKey(denom), bz) + if err != nil { + panic(err) + } } From 2436420c9e6b48e69b60ecbb8375cbb6b9ab6a79 Mon Sep 17 00:00:00 2001 From: MSalopek Date: Mon, 11 Nov 2024 15:51:10 +0100 Subject: [PATCH 3/5] fix: increase vbs for vbd --- x/staking/keeper/msg_server.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 0bdfc83ce408..c25a0ab08b35 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -800,6 +800,7 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS return nil, err } + // ValidatorBond delegation is not allowed for tokenize share if delegation.ValidatorBond { return nil, types.ErrValidatorBondNotAllowedForTokenizeShare } @@ -1005,6 +1006,19 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe return nil, types.ErrTinyRedemptionAmount } + // EDGECASE: tokenized share was transferred to a delegator with validator bond + // -> must increase validator bond shares + if delegation.ValidatorBond { + if err := k.IncreaseValidatorBondShares(ctx, valAddr, shares); err != nil { + return nil, err + } + // refetch the validator because the ValidatorBondShares have been updated + validator, err = k.GetValidator(ctx, valAddr) + if err != nil { + return nil, err + } + } + // If this redemption is NOT from a liquid staking provider, decrement the total liquid staked // If the redemption was from a liquid staking provider, the shares are still considered // liquid, even in their non-tokenized form (since they are owned by a liquid staking provider) From a0fd44665d1ee9aff267de6081c6f9f279f337da Mon Sep 17 00:00:00 2001 From: MSalopek Date: Wed, 13 Nov 2024 14:37:41 +0100 Subject: [PATCH 4/5] chore: add lsm testing & docs --- .../staking/keeper/delegation_test.go | 114 ++++++++++++++++++ x/staking/client/cli/tx.go | 4 +- x/staking/keeper/msg_server.go | 26 ++-- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/tests/integration/staking/keeper/delegation_test.go b/tests/integration/staking/keeper/delegation_test.go index 2f9912a5d974..1a07e83c8fae 100644 --- a/tests/integration/staking/keeper/delegation_test.go +++ b/tests/integration/staking/keeper/delegation_test.go @@ -293,3 +293,117 @@ func TestValidatorBondRedelegate(t *testing.T) { validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) require.Equal(t, validator.ValidatorBondShares, math.LegacyZeroDec()) } + +func TestSendTokenizedSharesToValidatorBondedAndRedeem(t *testing.T) { + t.Parallel() + f := initFixture(t) + ctx := f.sdkCtx + + addrDels := simtestutil.AddTestAddrs(f.bankKeeper, f.stakingKeeper, ctx, 2, f.stakingKeeper.TokensFromConsensusPower(ctx, 10000)) + + addrVals := simtestutil.ConvertAddrsToValAddrs(addrDels) + + startTokens := f.stakingKeeper.TokensFromConsensusPower(ctx, 10) + + bondDenom, err := f.stakingKeeper.BondDenom(ctx) + require.NoError(t, err) + notBondedPool := f.stakingKeeper.GetNotBondedPool(ctx) + + require.NoError(t, banktestutil.FundModuleAccount(ctx, f.bankKeeper, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens)))) + f.accountKeeper.SetModuleAccount(ctx, notBondedPool) + + // create a validator and a delegator to that validator + validator := testutil.NewValidator(t, addrVals[0], PKs[0]) + validator.Status = types.Bonded + f.stakingKeeper.SetValidator(ctx, validator) + + // set validator bond factor + params, err := f.stakingKeeper.GetParams(ctx) + require.NoError(t, err) + params.ValidatorBondFactor = math.LegacyNewDec(1) + f.stakingKeeper.SetParams(ctx, params) + + // convert to validator self-bond + msgServer := keeper.NewMsgServerImpl(f.stakingKeeper) + + validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *f.stakingKeeper, addrDels[0], startTokens, validator) + require.NoError(t, err) + _, err = msgServer.ValidatorBond(ctx, &types.MsgValidatorBond{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + }) + require.NoError(t, err) + + // confirm that the delegation is marked as validator bond + delegation1, err := f.stakingKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.NoError(t, err) + require.True(t, delegation1.ValidatorBond) + + // tokenize share for 2nd account delegation + validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *f.stakingKeeper, addrDels[1], startTokens, validator) + require.NoError(t, err) + // confirm that the delegation was NOT marked as validator bond + delegation2, err := f.stakingKeeper.GetDelegation(ctx, addrDels[1], addrVals[0]) + require.NoError(t, err) + require.False(t, delegation2.ValidatorBond) + + // confirm that the ValidatorBond delegation cannot be tokenized + _, err = msgServer.TokenizeShares(ctx, &types.MsgTokenizeShares{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + TokenizedShareOwner: addrDels[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.Error(t, err) + require.EqualError(t, err, "validator bond delegation is not allowed to tokenize share") + + // tokenize share for 2nd account delegation + tokenizeShareResp, err := msgServer.TokenizeShares(ctx, &types.MsgTokenizeShares{ + DelegatorAddress: addrDels[1].String(), + TokenizedShareOwner: addrDels[1].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.NoError(t, err) + + // transfer tokenized shares (as coins) to the delegator with validator bond + err = f.bankKeeper.SendCoins(ctx, addrDels[1], addrDels[0], sdk.NewCoins(tokenizeShareResp.Amount)) + require.NoError(t, err) + + // confirm that the tokenized shares are now owned by the delegator with validator bond + balanceSender := f.bankKeeper.GetBalance(ctx, addrDels[1], tokenizeShareResp.Amount.Denom) + require.Equal(t, balanceSender.Amount, math.ZeroInt()) + balanceReceiver := f.bankKeeper.GetBalance(ctx, addrDels[0], tokenizeShareResp.Amount.Denom) + require.Equal(t, balanceReceiver.Amount, tokenizeShareResp.Amount.Amount) + + // redeem shares and assert that the validator bond shares are increased + validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) + beforeRedeemShares := validator.ValidatorBondShares + + redeemResp, err := msgServer.RedeemTokensForShares(ctx, &types.MsgRedeemTokensForShares{ + DelegatorAddress: addrDels[0].String(), + Amount: tokenizeShareResp.Amount, + }) + require.NoError(t, err) + + validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) + afterRedeemShares := validator.ValidatorBondShares + require.True(t, afterRedeemShares.GT(beforeRedeemShares)) + require.Equal(t, afterRedeemShares, beforeRedeemShares.Add(math.LegacyNewDecFromInt(tokenizeShareResp.Amount.Amount))) + + // undelegate the delegator with ValidatorBond and assert that the validator bond shares are decreased + _, err = msgServer.Undelegate(ctx, &types.MsgUndelegate{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, redeemResp.Amount.Amount), + }) + require.NoError(t, err) + + validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0]) + afterUndelegateShares := validator.ValidatorBondShares + require.True(t, afterUndelegateShares.LT(afterRedeemShares)) + require.Equal(t, afterUndelegateShares, afterRedeemShares.Sub(math.LegacyNewDecFromInt(redeemResp.Amount.Amount))) + require.Equal(t, afterUndelegateShares, beforeRedeemShares) +} diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 00e918c99a2f..a3352d519496 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -856,10 +856,10 @@ $ %s tx staking enable-tokenize-shares --from mykey func NewValidatorBondCmd() *cobra.Command { cmd := &cobra.Command{ Use: "validator-bond [validator]", - Short: "Mark a delegation as a validator self-bond", + Short: "Mark a delegation as a validator bond.", Args: cobra.ExactArgs(1), Long: strings.TrimSpace( - fmt.Sprintf(`Mark a delegation as a validator self-bond. + fmt.Sprintf(`Mark a delegation as a validator bond. This can be done by any delegator but it is recommended to be done from the validator's delegation address. Example: $ %s tx staking validator-bond cosmosvaloper13h5xdxhsdaugwdrkusf8lkgu406h8t62jkqv3h --from mykey diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index c25a0ab08b35..af9a5d9a6ab4 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -1006,19 +1006,6 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe return nil, types.ErrTinyRedemptionAmount } - // EDGECASE: tokenized share was transferred to a delegator with validator bond - // -> must increase validator bond shares - if delegation.ValidatorBond { - if err := k.IncreaseValidatorBondShares(ctx, valAddr, shares); err != nil { - return nil, err - } - // refetch the validator because the ValidatorBondShares have been updated - validator, err = k.GetValidator(ctx, valAddr) - if err != nil { - return nil, err - } - } - // If this redemption is NOT from a liquid staking provider, decrement the total liquid staked // If the redemption was from a liquid staking provider, the shares are still considered // liquid, even in their non-tokenized form (since they are owned by a liquid staking provider) @@ -1097,6 +1084,19 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe return nil, err } + // tokenized shares can be transferred from a validator that does not have validator bond to a delegator with validator bond + // in that case we need to increase the validator bond shares (same as during msgServer.Delegate) + newDelegation, err := k.GetDelegation(ctx, delegatorAddress, valAddr) + if err != nil { + return nil, err + } + + if newDelegation.ValidatorBond { + if err := k.IncreaseValidatorBondShares(ctx, valAddr, shares); err != nil { + return nil, err + } + } + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeRedeemShares, From a7be6f2dda129dc93646fc5ac8235735450ec257 Mon Sep 17 00:00:00 2001 From: MSalopek Date: Fri, 15 Nov 2024 12:19:48 +0100 Subject: [PATCH 5/5] fix!: lsm redelegation slashes --- x/staking/keeper/msg_server.go | 2 +- x/staking/keeper/slash.go | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index af9a5d9a6ab4..b7253b8d44f4 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -369,7 +369,7 @@ func (k msgServer) BeginRedelegate(ctx context.Context, msg *types.MsgBeginRedel } // If this is a validator self-bond, the new liquid delegation cannot fall below the self-bond * bond factor - // The delegation on the new validator will not a validator bond + // The delegation on the new validator will not be a validator bond if srcDelegation.ValidatorBond { if err := k.SafelyDecreaseValidatorBond(ctx, valSrcAddr, srcShares); err != nil { return nil, err diff --git a/x/staking/keeper/slash.go b/x/staking/keeper/slash.go index 38d2c484fc75..95ba395ecfc8 100644 --- a/x/staking/keeper/slash.go +++ b/x/staking/keeper/slash.go @@ -414,6 +414,29 @@ func (k Keeper) SlashRedelegation(ctx context.Context, srcValidator types.Valida } } + delegation, err := k.GetDelegation(ctx, delegatorAddress, valDstAddr) + if err != nil { + return math.ZeroInt(), err + } + + // if the delegator holds a validator bond to destination validator, decrease the destination validator bond shares + if delegation.ValidatorBond { + if err := k.SafelyDecreaseValidatorBond(ctx, valDstAddr, math.LegacyDec(totalSlashAmount)); err != nil { + return math.ZeroInt(), err + } + } + + // if this delegation is from a liquid staking provider (identified if the delegator + // is an ICA account), the global and validator liquid totals should be decremented + if k.DelegatorIsLiquidStaker(delegatorAddress) { + if err := k.DecreaseTotalLiquidStakedTokens(ctx, totalSlashAmount); err != nil { + return math.ZeroInt(), err + } + if _, err := k.DecreaseValidatorLiquidShares(ctx, valDstAddr, math.LegacyDec(totalSlashAmount)); err != nil { + return math.ZeroInt(), err + } + } + if err := k.burnBondedTokens(ctx, bondedBurnedAmount); err != nil { return math.ZeroInt(), err }