diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e756cf..28c03c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes - [815](https://github.com/persistenceOne/pstake-native/pull/815) Not escape merkle paths for proof verification. +- [822](https://github.com/persistenceOne/pstake-native/pull/822) liquidstake: redelegation to follow msg router instead + of keeper. ## [v2.12.0] - 2024-04-05 diff --git a/x/liquidstake/keeper/liquidstake.go b/x/liquidstake/keeper/liquidstake.go index aded0f73..17cd2436 100644 --- a/x/liquidstake/keeper/liquidstake.go +++ b/x/liquidstake/keeper/liquidstake.go @@ -443,6 +443,61 @@ func (k Keeper) UnbondWithCap( return lsmRedeemResp.Amount.Amount, nil } +// RedelegateWithCap is a wrapper to invoke stakingKeeper.Redelegate but account for +// the amount of liquid staked shares and check against liquid staking cap. +func (k Keeper) RedelegateWithCap( + ctx sdk.Context, + delegatorAddress sdk.AccAddress, + validatorSrc sdk.ValAddress, + validatorDst sdk.ValAddress, + bondAmt math.Int, +) (time.Time, error) { + msgRedelegate := &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorSrcAddress: validatorSrc.String(), + ValidatorDstAddress: validatorDst.String(), + Amount: sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), bondAmt), + } + handler := k.router.Handler(msgRedelegate) + if handler == nil { + k.Logger(ctx).Error("failed to find redelegate handler") + + return time.Time{}, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgTypeURL(msgRedelegate)) + } + res, err := handler(ctx, msgRedelegate) + if err != nil { + k.Logger(ctx).Error( + "failed to execute redelegate msg", + types.ErrorKeyVal, + err, + types.MsgKeyVal, + msgRedelegate.String(), + ) + + return time.Time{}, errorsmod.Wrapf(types.ErrRedelegateFailed, "failed to send redelegate msg with err: %v", err) + } + ctx.EventManager().EmitEvents(res.GetEvents()) + + if len(res.MsgResponses) != 1 { + return time.Time{}, errorsmod.Wrapf( + types.ErrInvalidResponse, + "expected msg response should be exactly 1, got: %v, responses: %v", + len(res.MsgResponses), res.MsgResponses, + ) + } + + var msgRedelegateResponse stakingtypes.MsgBeginRedelegateResponse + if err = k.cdc.Unmarshal(res.MsgResponses[0].Value, &msgRedelegateResponse); err != nil { + return time.Time{}, errorsmod.Wrapf( + sdkerrors.ErrJSONUnmarshal, + "cannot unmarshal redelegate tx response message: %v", + err, + ) + } + + return msgRedelegateResponse.CompletionTime, nil +} + // LSMDelegate captures a staked amount from existing delegation using LSM, re-stakes from proxyAcc and // mints stkXPRT worth of stk coin value according to NetAmount and performs LiquidDelegate. func (k Keeper) LSMDelegate( diff --git a/x/liquidstake/keeper/rebalancing.go b/x/liquidstake/keeper/rebalancing.go index 214816cc..43912180 100644 --- a/x/liquidstake/keeper/rebalancing.go +++ b/x/liquidstake/keeper/rebalancing.go @@ -29,7 +29,7 @@ func (k Keeper) TryRedelegation(ctx sdk.Context, re types.Redelegation) (complet } // calculate delShares from tokens with validation - shares, err := k.stakingKeeper.ValidateUnbondAmount( + _, err = k.stakingKeeper.ValidateUnbondAmount( ctx, re.Delegator, srcVal, re.Amount, ) if err != nil { @@ -37,11 +37,12 @@ func (k Keeper) TryRedelegation(ctx sdk.Context, re types.Redelegation) (complet } // when last, full redelegation of shares from delegation + amt := re.Amount if re.Last { - shares = re.SrcValidator.GetDelShares(ctx, k.stakingKeeper) + amt = re.SrcValidator.GetLiquidTokens(ctx, k.stakingKeeper, false) } cachedCtx, writeCache := ctx.CacheContext() - completionTime, err = k.stakingKeeper.BeginRedelegation(cachedCtx, re.Delegator, srcVal, dstVal, shares) + completionTime, err = k.RedelegateWithCap(cachedCtx, re.Delegator, srcVal, dstVal, amt) if err != nil { return time.Time{}, fmt.Errorf("failed to begin redelegation: %w", err) } diff --git a/x/liquidstake/keeper/rebalancing_test.go b/x/liquidstake/keeper/rebalancing_test.go index 20848155..9bded2de 100644 --- a/x/liquidstake/keeper/rebalancing_test.go +++ b/x/liquidstake/keeper/rebalancing_test.go @@ -211,17 +211,17 @@ func (s *KeeperTestSuite) TestRebalancingCase1() { reds = s.keeper.UpdateLiquidValidatorSet(s.ctx) s.Require().Len(reds, 1) - // all redelegated, no delShares + // all redelegated, no delShares ( exception, dust ) proxyAccDel2, found = s.app.StakingKeeper.GetDelegation(s.ctx, types.LiquidStakeProxyAcc, valOpers[1]) - s.Require().False(found) + s.Require().True(proxyAccDel2.Shares.LT(sdk.OneDec())) // liquid validator removed, invalid after tombstoned lvState, found = s.keeper.GetLiquidValidatorState(s.ctx, valOpers[1]) s.Require().True(found) s.Require().Equal(lvState.OperatorAddress, valOpers[1].String()) s.Require().Equal(lvState.Status, types.ValidatorStatusInactive) - s.Require().EqualValues(lvState.DelShares, sdk.ZeroDec()) - s.Require().EqualValues(lvState.LiquidTokens, sdk.ZeroInt()) + s.Require().True(proxyAccDel2.Shares.LT(sdk.OneDec())) + s.Require().True(lvState.LiquidTokens.Equal(sdk.ZeroInt())) // jail last liquid validator, undelegate all liquid tokens to proxy acc nasBefore := s.keeper.GetNetAmountState(s.ctx) diff --git a/x/liquidstake/types/errors.go b/x/liquidstake/types/errors.go index 46c68239..a3981ff9 100644 --- a/x/liquidstake/types/errors.go +++ b/x/liquidstake/types/errors.go @@ -28,4 +28,5 @@ var ( ErrUnbondFailed = errors.Register(ModuleName, 23, "unbond failed") ErrInvalidResponse = errors.Register(ModuleName, 24, "invalid response") ErrUnstakeFailed = errors.Register(ModuleName, 25, "Unstaking failed") + ErrRedelegateFailed = errors.Register(ModuleName, 26, "Redelegate failed") ) diff --git a/x/liquidstake/types/expected_keepers.go b/x/liquidstake/types/expected_keepers.go index a15a8041..7f253e9f 100644 --- a/x/liquidstake/types/expected_keepers.go +++ b/x/liquidstake/types/expected_keepers.go @@ -49,28 +49,14 @@ type StakingKeeper interface { delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation stakingtypes.Delegation, found bool) IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, fn func(index int64, delegation stakingtypes.DelegationI) (stop bool)) - Delegate( - ctx sdk.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc stakingtypes.BondStatus, - validator stakingtypes.Validator, subtractAccount bool, - ) (newShares math.LegacyDec, err error) BondDenom(ctx sdk.Context) (res string) - Unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares math.LegacyDec) (amount math.Int, err error) UnbondingTime(ctx sdk.Context) (res time.Duration) - SetUnbondingDelegationEntry( - ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress, - creationHeight int64, minTime time.Time, balance math.Int, - ) stakingtypes.UnbondingDelegation - InsertUBDQueue(ctx sdk.Context, ubd stakingtypes.UnbondingDelegation, - completionTime time.Time) ValidateUnbondAmount( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int, ) (shares math.LegacyDec, err error) GetUnbondingDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (stakingtypes.UnbondingDelegation, bool) GetAllUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress) []stakingtypes.UnbondingDelegation - BeginRedelegation( - ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount math.LegacyDec, - ) (completionTime time.Time, err error) GetAllRedelegations( ctx sdk.Context, delegator sdk.AccAddress, srcValAddress, dstValAddress sdk.ValAddress, ) []stakingtypes.Redelegation @@ -78,8 +64,6 @@ type StakingKeeper interface { BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate HasMaxUnbondingDelegationEntries(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) bool - SafelyIncreaseTotalLiquidStakedTokens(ctx sdk.Context, amount math.Int, sharesAlreadyBonded bool) error - DecreaseTotalLiquidStakedTokens(ctx sdk.Context, amount math.Int) error GetBondedPool(ctx sdk.Context) (bondedPool authtypes.ModuleAccountI) }