diff --git a/CHANGELOG.md b/CHANGELOG.md index 04105706..99aea351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements - [855](https://github.com/persistenceOne/pstake-native/pull/855) Add condition for not allowing zero delegation unbondings icq +- [900](https://github.com/persistenceOne/pstake-native/pull/900) Add feature to deprecate liquidstakeibc ## [v2.15.0] - 2024-05-30 diff --git a/x/liquidstakeibc/keeper/abci.go b/x/liquidstakeibc/keeper/abci.go index f497cb64..34f1519a 100644 --- a/x/liquidstakeibc/keeper/abci.go +++ b/x/liquidstakeibc/keeper/abci.go @@ -18,17 +18,14 @@ import ( func (k *Keeper) BeginBlock(ctx sdk.Context) { // perform BeginBlocker tasks for each chain for _, hc := range k.GetAllHostChains(ctx) { - if !hc.Active { - // don't do anything on inactive chains - continue + if hc.Active { + // attempt to delegate + k.DoDelegate(ctx, hc) } // attempt to recreate closed ICA channels k.DoRecreateICA(ctx, hc) - // attempt to delegate - k.DoDelegate(ctx, hc) - // attempt to automatically claim matured undelegations k.DoClaim(ctx, hc) diff --git a/x/liquidstakeibc/keeper/icq.go b/x/liquidstakeibc/keeper/icq.go index 5bb8759a..f9b5e2d0 100644 --- a/x/liquidstakeibc/keeper/icq.go +++ b/x/liquidstakeibc/keeper/icq.go @@ -114,8 +114,9 @@ func DelegationCallback(k Keeper, ctx sdk.Context, data []byte, query icqtypes.Q k.SetHostChainValidator(ctx, hc, validator) // update the c value for the slashed chain - k.UpdateCValue(ctx, hc) - + if hc.Active { + k.UpdateCValue(ctx, hc) + } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSlashing, diff --git a/x/liquidstakeibc/keeper/keeper.go b/x/liquidstakeibc/keeper/keeper.go index dfdac7bf..de4d5796 100644 --- a/x/liquidstakeibc/keeper/keeper.go +++ b/x/liquidstakeibc/keeper/keeper.go @@ -255,7 +255,9 @@ func (k *Keeper) UpdateCValues(ctx sdk.Context) { hostChains := k.GetAllHostChains(ctx) for _, hc := range hostChains { - k.UpdateCValue(ctx, hc) + if hc.Active { + k.UpdateCValue(ctx, hc) + } } } diff --git a/x/liquidstakeibc/keeper/msg_server.go b/x/liquidstakeibc/keeper/msg_server.go index 3467c5f7..133a1d84 100644 --- a/x/liquidstakeibc/keeper/msg_server.go +++ b/x/liquidstakeibc/keeper/msg_server.go @@ -13,6 +13,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/gogoproto/proto" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/persistenceOne/pstake-native/v2/x/liquidstakeibc/types" @@ -331,7 +332,75 @@ func (k msgServer) UpdateHostChain( if err := k.QueryValidatorDelegationUpdate(ctx, hc, val); err != nil { return nil, fmt.Errorf("unable to send ICQ query for validator delegation update") } + case types.KeyForceUnbond: + if hc.Active { + return nil, fmt.Errorf("cannot force unbond to active host chain") + } + validator, coin, valid := strings.Cut(update.Value, ",") + if !valid { + return nil, fmt.Errorf("invalid force unbond value, expected form \"cosmovaloperxx,123denom\" ") + } + val, found := hc.GetValidator(validator) + if !found { + return nil, types.ErrValidatorNotFound + } + amount, err := sdktypes.ParseCoinNormalized(coin) + if err != nil { + return nil, err + } + msgUnbond := &stakingtypes.MsgUndelegate{ + DelegatorAddress: hc.DelegationAccount.Address, + ValidatorAddress: val.OperatorAddress, + Amount: amount, + } + _, err = k.GenerateAndExecuteICATx(ctx, hc.ConnectionId, hc.DelegationAccount.Owner, []proto.Message{msgUnbond}) + if err != nil { + return nil, err + } + case types.KeyForceICATransfer: + amount, err := sdktypes.ParseCoinNormalized(update.Value) + if err != nil { + return nil, err + } + _, err = k.SendICATransfer(ctx, hc, amount, hc.DelegationAccount.Address, k.GetParams(ctx).AdminAddress, hc.DelegationAccount.Owner) + if err != nil { + return nil, err + } + case types.KeyForceICATransferRewards: + amount, err := sdktypes.ParseCoinNormalized(update.Value) + if err != nil { + return nil, err + } + _, err = k.SendICATransfer(ctx, hc, amount, hc.RewardsAccount.Address, k.GetParams(ctx).AdminAddress, hc.RewardsAccount.Owner) + if err != nil { + return nil, err + } + case types.KeyForceTransferDeposits: + amount := k.bankKeeper.GetBalance(ctx, k.GetDepositModuleAccount(ctx).GetAddress(), hc.IBCDenom()) + if amount.IsPositive() { + err := k.bankKeeper.SendCoins(ctx, k.GetDepositModuleAccount(ctx).GetAddress(), sdktypes.MustAccAddressFromBech32(k.GetParams(ctx).AdminAddress), sdktypes.NewCoins(amount)) + if err != nil { + return nil, err + } + } + case types.KeyForceTransferUnbonded: + amount := k.bankKeeper.GetBalance(ctx, k.GetUndelegationModuleAccount(ctx).GetAddress(), hc.IBCDenom()) + if amount.IsPositive() { + err := k.bankKeeper.SendCoins(ctx, k.GetUndelegationModuleAccount(ctx).GetAddress(), sdktypes.MustAccAddressFromBech32(k.GetParams(ctx).AdminAddress), sdktypes.NewCoins(amount)) + if err != nil { + return nil, err + } + } + case types.KeyForceFailUnbond: + epochNumber, err := strconv.ParseInt(update.Value, 10, 64) + if err != nil { + return nil, err + } + err = k.FailUnbondingsForEpoch(ctx, hc.ChainId, epochNumber) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("invalid or unexpected update key: %s", update.Key) } diff --git a/x/liquidstakeibc/keeper/unbonding.go b/x/liquidstakeibc/keeper/unbonding.go index 00ca2b0a..4f9ea21d 100644 --- a/x/liquidstakeibc/keeper/unbonding.go +++ b/x/liquidstakeibc/keeper/unbonding.go @@ -88,6 +88,16 @@ func (k *Keeper) FailAllUnbondingsForSequenceID(ctx sdk.Context, sequenceID stri } } +func (k *Keeper) FailUnbondingsForEpoch(ctx sdk.Context, chainID string, epochNumber int64) error { + unbonding, found := k.GetUnbonding(ctx, chainID, epochNumber) + if !found { + return types.ErrUnbondingNotFound + } + unbonding.State = types.Unbonding_UNBONDING_FAILED + k.SetUnbonding(ctx, unbonding) + return nil +} + func (k *Keeper) RevertUnbondingsState(ctx sdk.Context, unbondings []*types.Unbonding) { for _, unbonding := range unbondings { unbonding.IbcSequenceId = "" diff --git a/x/liquidstakeibc/types/errors.go b/x/liquidstakeibc/types/errors.go index 5d38907f..63e61521 100644 --- a/x/liquidstakeibc/types/errors.go +++ b/x/liquidstakeibc/types/errors.go @@ -26,4 +26,5 @@ var ( ErrLSMDepositProcessing = errorsmod.Register(ModuleName, 2020, "already processing LSM deposit") ErrLSMValidatorInvalidState = errorsmod.Register(ModuleName, 2021, "validator invalid state") ErrInsufficientDeposits = errorsmod.Register(ModuleName, 2022, "insufficient deposits") + ErrUnbondingNotFound = errorsmod.Register(ModuleName, 2023, "unbonding not found") ) diff --git a/x/liquidstakeibc/types/events.go b/x/liquidstakeibc/types/events.go index 4f6b3c7c..d941a533 100644 --- a/x/liquidstakeibc/types/events.go +++ b/x/liquidstakeibc/types/events.go @@ -5,6 +5,7 @@ const ( EventTypeLiquidStakeLSM = "liquid_stake_lsm" EventTypeLiquidUnstake = "liquid_unstake" EventTypeRedeem = "redeem" + EventTypeRedeemDeprecated = "redeem_deprecated" EventTypePacket = "ics27_packet" EventTypeTimeout = "timeout" EventTypeSlashing = "validator_slash" diff --git a/x/liquidstakeibc/types/keys.go b/x/liquidstakeibc/types/keys.go index a7f730e9..1894595a 100644 --- a/x/liquidstakeibc/types/keys.go +++ b/x/liquidstakeibc/types/keys.go @@ -87,6 +87,12 @@ const ( KeyFlags string = "flags" KeyRewardParams string = "reward_params" KeyForceUpdateValidator string = "force_update_validator" + KeyForceUnbond string = "force_unbond" + KeyForceICATransfer string = "force_ica_transfer" + KeyForceICATransferRewards string = "force_ica_transfer_rewards" + KeyForceTransferDeposits string = "force_transfer_deposits" + KeyForceTransferUnbonded string = "force_transfer_unbonded" + KeyForceFailUnbond string = "force_fail_unbond" ) var ( diff --git a/x/liquidstakeibc/types/msgs.go b/x/liquidstakeibc/types/msgs.go index 810ba984..ea22e8f2 100644 --- a/x/liquidstakeibc/types/msgs.go +++ b/x/liquidstakeibc/types/msgs.go @@ -33,6 +33,7 @@ var ( _ sdk.Msg = &MsgLiquidUnstake{} _ sdk.Msg = &MsgRedeem{} _ sdk.Msg = &MsgLiquidStakeLSM{} + _ sdk.Msg = &MsgUpdateParams{} ) func NewMsgRegisterHostChain( @@ -396,6 +397,48 @@ func (m *MsgUpdateHostChain) ValidateBasic() error { if err != nil { return err } + case KeyForceUnbond: + // expected "chainvaloper1xxx,1234denom" + validator, coin, valid := strings.Cut(update.Value, ",") + if !valid { + return fmt.Errorf("invalid force unbond value, expected form \"cosmovaloperxx,123denom\" ") + } + _, _, err := bech32.DecodeAndConvert(validator) + if err != nil { + return err + } + _, err = sdk.ParseCoinNormalized(coin) + if err != nil { + return err + } + case KeyForceICATransfer: + // expected "1234denom" + _, err := sdk.ParseCoinNormalized(update.Value) + if err != nil { + return err + } + case KeyForceICATransferRewards: + // expected "1234denom" + _, err := sdk.ParseCoinNormalized(update.Value) + if err != nil { + return err + } + case KeyForceTransferDeposits: + // expected nothing, "" + if update.Value != "" { + return fmt.Errorf("invalid force transfer deposits, expected \"\" ") + } + case KeyForceTransferUnbonded: + // expected nothing, "" + if update.Value != "" { + return fmt.Errorf("invalid force transfer unbonded, expected \"\" ") + } + case KeyForceFailUnbond: + // expected , "123" + _, err := strconv.ParseInt(update.Value, 10, 64) + if err != nil { + return err + } default: return fmt.Errorf("invalid or unexpected update key: %s", update.Key) }