Skip to content

Commit

Permalink
Undel host function (#935)
Browse files Browse the repository at this point in the history
Co-authored-by: sampocs <[email protected]>
  • Loading branch information
riley-stride and sampocs authored Sep 18, 2023
1 parent 60c18e7 commit 40a3853
Show file tree
Hide file tree
Showing 18 changed files with 1,782 additions and 163 deletions.
8 changes: 8 additions & 0 deletions proto/stride/stakeibc/callbacks.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ message UndelegateCallback {
repeated uint64 epoch_unbonding_record_ids = 3;
}

message UndelegateHostCallback {
string amt = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
repeated SplitDelegation split_delegations = 2;
}

message RedemptionCallback {
string host_zone_id = 1;
repeated uint64 epoch_unbonding_record_ids = 2;
Expand Down
10 changes: 10 additions & 0 deletions proto/stride/stakeibc/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ service Msg {
rpc CalibrateDelegation(MsgCalibrateDelegation)
returns (MsgCalibrateDelegationResponse);
rpc ClearBalance(MsgClearBalance) returns (MsgClearBalanceResponse);
rpc UndelegateHost(MsgUndelegateHost) returns (MsgUndelegateHostResponse);
rpc UpdateInnerRedemptionRateBounds(MsgUpdateInnerRedemptionRateBounds)
returns (MsgUpdateInnerRedemptionRateBoundsResponse);
}
Expand Down Expand Up @@ -172,6 +173,15 @@ message MsgUpdateValidatorSharesExchRate {
string valoper = 3;
}
message MsgUpdateValidatorSharesExchRateResponse {}
message MsgUndelegateHost {
string creator = 1;
string amount = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];

}
message MsgUndelegateHostResponse {}

message MsgCalibrateDelegation {
string creator = 1;
Expand Down
1 change: 1 addition & 0 deletions x/stakeibc/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(CmdUpdateValidatorSharesExchRate())
cmd.AddCommand(CmdCalibrateDelegation())
cmd.AddCommand(CmdClearBalance())
cmd.AddCommand(CmdUndelegateHost())
cmd.AddCommand(CmdUpdateInnerRedemptionRateBounds())

return cmd
Expand Down
49 changes: 49 additions & 0 deletions x/stakeibc/client/cli/tx_undelegate_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cli

import (
"strconv"

errorsmod "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/spf13/cobra"

"github.com/Stride-Labs/stride/v14/x/stakeibc/types"
)

var _ = strconv.Itoa(0)

func CmdUndelegateHost() *cobra.Command {
cmd := &cobra.Command{
Use: "undelegate-host [amount]",
Short: "Broadcast message undelegate-host",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
argAmount, found := sdk.NewIntFromString(args[0])
if !found {
return errorsmod.Wrap(sdkerrors.ErrInvalidType, "can not convert string to int")
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := types.NewMsgUndelegateHost(
clientCtx.GetFromAddress().String(),
argAmount,
)
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
3 changes: 3 additions & 0 deletions x/stakeibc/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func NewMessageHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgUpdateValidatorSharesExchRate:
res, err := msgServer.UpdateValidatorSharesExchRate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgUndelegateHost:
res, err := msgServer.UndelegateHost(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgCalibrateDelegation:
res, err := msgServer.CalibrateDelegation(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
Expand Down
16 changes: 9 additions & 7 deletions x/stakeibc/keeper/icacallbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import (
)

const (
ICACallbackID_Delegate = "delegate"
ICACallbackID_Claim = "claim"
ICACallbackID_Undelegate = "undelegate"
ICACallbackID_Reinvest = "reinvest"
ICACallbackID_Redemption = "redemption"
ICACallbackID_Rebalance = "rebalance"
ICACallbackID_Detokenize = "detokenize"
ICACallbackID_Delegate = "delegate"
ICACallbackID_Claim = "claim"
ICACallbackID_Undelegate = "undelegate"
ICACallbackID_UndelegateHost = "undelegatehost"
ICACallbackID_Reinvest = "reinvest"
ICACallbackID_Redemption = "redemption"
ICACallbackID_Rebalance = "rebalance"
ICACallbackID_Detokenize = "detokenize"
)

func (k Keeper) Callbacks() icacallbackstypes.ModuleCallbacks {
return []icacallbackstypes.ICACallback{
{CallbackId: ICACallbackID_Delegate, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.DelegateCallback)},
{CallbackId: ICACallbackID_Claim, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.ClaimCallback)},
{CallbackId: ICACallbackID_Undelegate, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.UndelegateCallback)},
{CallbackId: ICACallbackID_UndelegateHost, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.UndelegateHostCallback)},
{CallbackId: ICACallbackID_Reinvest, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.ReinvestCallback)},
{CallbackId: ICACallbackID_Redemption, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.RedemptionCallback)},
{CallbackId: ICACallbackID_Rebalance, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.RebalanceCallback)},
Expand Down
83 changes: 83 additions & 0 deletions x/stakeibc/keeper/icacallbacks_undelegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,86 @@ func (k Keeper) BurnTokens(ctx sdk.Context, hostZone types.HostZone, stTokenBurn
k.Logger(ctx).Info(fmt.Sprintf("Total supply %s", k.bankKeeper.GetSupply(ctx, stCoinDenom)))
return nil
}

// ICA Callback after undelegating host
//
// If successful:
// * sets SetUndelegateHostPrevented
// If timeout:
// * Does nothing
// If failure:
// * Does nothing
func (k Keeper) UndelegateHostCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error {
// Fetch callback args
var undelegateHostCallback types.UndelegateHostCallback
if err := proto.Unmarshal(args, &undelegateHostCallback); err != nil {
return errorsmod.Wrapf(types.ErrUnmarshalFailure, fmt.Sprintf("Unable to unmarshal undelegate host callback args: %s", err.Error()))
}
k.Logger(ctx).Info("Starting undelegate host callback for amount %v%s", undelegateHostCallback.Amt)

// Regardless of failure/success/timeout, indicate that this ICA has completed
hostZone, found := k.GetHostZone(ctx, EvmosHostZoneChainId)
if !found {
return errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "Host zone not found: %s", EvmosHostZoneChainId)
}
for _, splitDelegation := range undelegateHostCallback.SplitDelegations {
if err := k.DecrementValidatorDelegationChangesInProgress(&hostZone, splitDelegation.Validator); err != nil {
// TODO: Revert after v14 upgrade
if errors.Is(err, types.ErrInvalidValidatorDelegationUpdates) {
k.Logger(ctx).Error(utils.LogICACallbackWithHostZone(EvmosHostZoneChainId, ICACallbackID_Undelegate,
"Invariant failed - delegation changes in progress fell below 0 for %s", splitDelegation.Validator))
continue
}
return err
}
}
k.SetHostZone(ctx, hostZone)

// Check for timeout (ack nil)
if ackResponse.Status == icacallbackstypes.AckResponseStatus_TIMEOUT {
k.Logger(ctx).Error("UndelegateHostCallback Timeout:", icacallbackstypes.AckResponseStatus_TIMEOUT, packet)
return nil
}

// Check for a failed transaction (ack error)
if ackResponse.Status == icacallbackstypes.AckResponseStatus_FAILURE {
k.Logger(ctx).Error("UndelegateHostCallback failure (ack error):", icacallbackstypes.AckResponseStatus_FAILURE, packet)
return nil
}

// 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("UndelegateHostCallback success:", icacallbackstypes.AckResponseStatus_SUCCESS, packet)

// Update delegation balances
err := k.UpdateDelegationBalancesHost(ctx, evmosHost, undelegateHostCallback)
if err != nil {
k.Logger(ctx).Error(fmt.Sprintf("UndelegateCallback | %s", err.Error()))
return err
}

k.Logger(ctx).Info("UndelegateHostCallback: SetUndelegateHostPrevented")
if err := k.SetUndelegateHostPrevented(ctx); err != nil {
k.Logger(ctx).Error(fmt.Sprintf("UndelegateHostCallback failed due to SetUndelegateHostPrevented | %s", err.Error()))
return err
}

return nil
}

// Decrement the delegation field on host and each validator's delegations after a successful unbonding ICA
func (k Keeper) UpdateDelegationBalancesHost(ctx sdk.Context, hostZone types.HostZone, undelegateHostCallback types.UndelegateHostCallback) error {
// Undelegate from each validator and update Evmos staked balance, if successful
for _, undelegation := range undelegateHostCallback.SplitDelegations {
err := k.AddDelegationToValidator(ctx, &hostZone, undelegation.Validator, undelegation.Amount.Neg(), ICACallbackID_UndelegateHost)
if err != nil {
return err
}
}
k.SetHostZone(ctx, hostZone)
return nil
}
Loading

0 comments on commit 40a3853

Please sign in to comment.