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

add inner bounds #938

Merged
merged 14 commits into from
Sep 18, 2023
10 changes: 10 additions & 0 deletions proto/stride/stakeibc/host_zone.proto
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ message HostZone {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string min_inner_redemption_rate = 28 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string max_inner_redemption_rate = 29 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
bool lsm_liquid_stake_enabled = 27;
bool halted = 19;
reserved 4, 5, 6, 7, 14, 15, 16;
Expand Down
19 changes: 19 additions & 0 deletions proto/stride/stakeibc/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,27 @@ service Msg {
rpc UpdateValidatorSharesExchRate(MsgUpdateValidatorSharesExchRate)
returns (MsgUpdateValidatorSharesExchRateResponse);
rpc ClearBalance(MsgClearBalance) returns (MsgClearBalanceResponse);
rpc UpdateInnerRedemptionRateBounds(MsgUpdateInnerRedemptionRateBounds)
returns (MsgUpdateInnerRedemptionRateBoundsResponse);
}

message MsgUpdateInnerRedemptionRateBounds {
string creator = 1;
string chain_id = 2;
string min_inner_redemption_rate = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string max_inner_redemption_rate = 4 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

message MsgUpdateInnerRedemptionRateBoundsResponse {}

message MsgLiquidStake {
string creator = 1;
string amount = 2 [
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 @@ -36,6 +36,7 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(CmdRestoreInterchainAccount())
cmd.AddCommand(CmdUpdateValidatorSharesExchRate())
cmd.AddCommand(CmdClearBalance())
cmd.AddCommand(CmdUpdateInnerRedemptionRateBounds())

return cmd
}
44 changes: 44 additions & 0 deletions x/stakeibc/client/cli/tx_update_inner_redemption_rate_bounds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cli

import (
"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"
"github.com/spf13/cobra"

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

func CmdUpdateInnerRedemptionRateBounds() *cobra.Command {
cmd := &cobra.Command{
Use: "update-tight-bounds [chainid] [min-bound] [max-bound]",
Short: "Broadcast message update-tight-bounds",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
argChainId := args[0]
minInnerRedemptionRate := sdk.MustNewDecFromStr(args[1])
maxInnerRedemptionRate := sdk.MustNewDecFromStr(args[2])

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

msg := types.NewMsgUpdateInnerRedemptionRateBounds(
clientCtx.GetFromAddress().String(),
argChainId,
minInnerRedemptionRate,
maxInnerRedemptionRate,
)
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.MsgUpdateInnerRedemptionRateBounds:
res, err := msgServer.UpdateInnerRedemptionRateBounds(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg)
return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
Expand Down
2 changes: 2 additions & 0 deletions x/stakeibc/keeper/host_zone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ func createNHostZone(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.Host
items[i].LastRedemptionRate = sdk.NewDec(1)
items[i].MinRedemptionRate = sdk.NewDecWithPrec(5, 1)
items[i].MaxRedemptionRate = sdk.NewDecWithPrec(15, 1)
items[i].MinInnerRedemptionRate = sdk.NewDecWithPrec(5, 1)
items[i].MaxInnerRedemptionRate = sdk.NewDecWithPrec(15, 1)
items[i].TotalDelegations = sdkmath.ZeroInt()
keeper.SetHostZone(ctx, items[i])
}
Expand Down
46 changes: 40 additions & 6 deletions x/stakeibc/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,33 @@ func (k Keeper) GetICATimeoutNanos(ctx sdk.Context, epochType string) (uint64, e

// safety check: ensure the redemption rate is NOT below our min safety threshold && NOT above our max safety threshold on host zone
func (k Keeper) IsRedemptionRateWithinSafetyBounds(ctx sdk.Context, zone types.HostZone) (bool, error) {
// Get the wide bounds
minSafetyThreshold, maxSafetyThreshold := k.GetOuterSafetyBounds(ctx, zone)

redemptionRate := zone.RedemptionRate

if redemptionRate.LT(minSafetyThreshold) || redemptionRate.GT(maxSafetyThreshold) {
errMsg := fmt.Sprintf("IsRedemptionRateWithinSafetyBounds check failed %s is outside safety bounds [%s, %s]", redemptionRate.String(), minSafetyThreshold.String(), maxSafetyThreshold.String())
k.Logger(ctx).Error(errMsg)
return false, errorsmod.Wrapf(types.ErrRedemptionRateOutsideSafetyBounds, errMsg)
}

// Verify the redemption rate is within the inner safety bounds
// The inner safety bounds should always be within the safety bounds, but
// the redundancy above is cheap.
// There is also one scenario where the outer bounds go within the inner bounds - if they're updated as part of a param change proposal.
minInnerSafetyThreshold, maxInnerSafetyThreshold := k.GetInnerSafetyBounds(ctx, zone)
if redemptionRate.LT(minInnerSafetyThreshold) || redemptionRate.GT(maxInnerSafetyThreshold) {
errMsg := fmt.Sprintf("IsRedemptionRateWithinSafetyBounds check failed %s is outside inner safety bounds [%s, %s]", redemptionRate.String(), minInnerSafetyThreshold.String(), maxInnerSafetyThreshold.String())
k.Logger(ctx).Error(errMsg)
return false, errorsmod.Wrapf(types.ErrRedemptionRateOutsideSafetyBounds, errMsg)
}

return true, nil
}

func (k Keeper) GetOuterSafetyBounds(ctx sdk.Context, zone types.HostZone) (sdk.Dec, sdk.Dec) {
sampocs marked this conversation as resolved.
Show resolved Hide resolved
// Fetch the wide bounds
minSafetyThresholdInt := k.GetParam(ctx, types.KeyDefaultMinRedemptionRateThreshold)
minSafetyThreshold := sdk.NewDec(int64(minSafetyThresholdInt)).Quo(sdk.NewDec(100))

Expand All @@ -268,12 +295,19 @@ func (k Keeper) IsRedemptionRateWithinSafetyBounds(ctx sdk.Context, zone types.H
maxSafetyThreshold = zone.MaxRedemptionRate
}

redemptionRate := zone.RedemptionRate
return minSafetyThreshold, maxSafetyThreshold
}

if redemptionRate.LT(minSafetyThreshold) || redemptionRate.GT(maxSafetyThreshold) {
errMsg := fmt.Sprintf("IsRedemptionRateWithinSafetyBounds check failed %s is outside safety bounds [%s, %s]", redemptionRate.String(), minSafetyThreshold.String(), maxSafetyThreshold.String())
k.Logger(ctx).Error(errMsg)
return false, errorsmod.Wrapf(types.ErrRedemptionRateOutsideSafetyBounds, errMsg)
func (k Keeper) GetInnerSafetyBounds(ctx sdk.Context, zone types.HostZone) (sdk.Dec, sdk.Dec) {
// Fetch the inner bounds
minSafetyThreshold, maxSafetyThreshold := k.GetOuterSafetyBounds(ctx, zone)

if !zone.MinInnerRedemptionRate.IsNil() && zone.MinInnerRedemptionRate.IsPositive() && zone.MinInnerRedemptionRate.GT(minSafetyThreshold) {
minSafetyThreshold = zone.MinInnerRedemptionRate
}
return true, nil
if !zone.MaxInnerRedemptionRate.IsNil() && zone.MaxInnerRedemptionRate.IsPositive() && zone.MaxInnerRedemptionRate.LT(maxSafetyThreshold) {
maxSafetyThreshold = zone.MaxInnerRedemptionRate
}

return minSafetyThreshold, maxSafetyThreshold
}
17 changes: 10 additions & 7 deletions x/stakeibc/keeper/msg_server_register_host_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,16 @@ func (k msgServer) RegisterHostZone(goCtx context.Context, msg *types.MsgRegiste
HostDenom: msg.HostDenom,
TransferChannelId: msg.TransferChannelId,
// Start sharesToTokens rate at 1 upon registration
RedemptionRate: sdk.NewDec(1),
LastRedemptionRate: sdk.NewDec(1),
UnbondingPeriod: msg.UnbondingPeriod,
DepositAddress: depositAddress.String(),
MinRedemptionRate: msg.MinRedemptionRate,
MaxRedemptionRate: msg.MaxRedemptionRate,
LsmLiquidStakeEnabled: msg.LsmLiquidStakeEnabled,
RedemptionRate: sdk.NewDec(1),
LastRedemptionRate: sdk.NewDec(1),
UnbondingPeriod: msg.UnbondingPeriod,
DepositAddress: depositAddress.String(),
MinRedemptionRate: msg.MinRedemptionRate,
MaxRedemptionRate: msg.MaxRedemptionRate,
// Default the inner bounds to the outer bounds
MinInnerRedemptionRate: msg.MinRedemptionRate,
MaxInnerRedemptionRate: msg.MaxRedemptionRate,
LsmLiquidStakeEnabled: msg.LsmLiquidStakeEnabled,
}
// write the zone back to the store
k.SetHostZone(ctx, zone)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package keeper

import (
"context"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

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

func (k msgServer) UpdateInnerRedemptionRateBounds(goCtx context.Context, msg *types.MsgUpdateInnerRedemptionRateBounds) (*types.MsgUpdateInnerRedemptionRateBoundsResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

// Confirm host zone exists
zone, found := k.GetHostZone(ctx, msg.ChainId)
if !found {
k.Logger(ctx).Error(fmt.Sprintf("Host Zone not found: %s", msg.ChainId))
return nil, types.ErrInvalidHostZone
}

// Get the wide bounds
outerMinSafetyThreshold, outerMaxSafetyThreshold := k.GetOuterSafetyBounds(ctx, zone)

innerMinSafetyThreshold := msg.MinInnerRedemptionRate
innerMaxSafetyThreshold := msg.MaxInnerRedemptionRate

// Confirm the inner bounds are within the outer bounds
if innerMinSafetyThreshold.LT(outerMinSafetyThreshold) {
k.Logger(ctx).Error(fmt.Sprintf("Inner min safety threshold (%s) is less than outer min safety threshold (%s)", innerMinSafetyThreshold, outerMinSafetyThreshold))
return nil, types.ErrInvalidBounds
}

if innerMaxSafetyThreshold.GT(outerMaxSafetyThreshold) {
k.Logger(ctx).Error(fmt.Sprintf("Inner max safety threshold (%s) is greater than outer max safety threshold (%s)", innerMaxSafetyThreshold, outerMaxSafetyThreshold))
return nil, types.ErrInvalidBounds
}

// Set the inner bounds on the host zone
zone.MinInnerRedemptionRate = innerMinSafetyThreshold
zone.MaxInnerRedemptionRate = innerMaxSafetyThreshold
k.SetHostZone(ctx, zone)

return &types.MsgUpdateInnerRedemptionRateBoundsResponse{}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package keeper
2 changes: 2 additions & 0 deletions x/stakeibc/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&ToggleLSMProposal{}, "stakeibc/ToggleLSMProposal", nil)
cdc.RegisterConcrete(&MsgRestoreInterchainAccount{}, "stakeibc/RestoreInterchainAccount", nil)
cdc.RegisterConcrete(&MsgUpdateValidatorSharesExchRate{}, "stakeibc/UpdateValidatorSharesExchRate", nil)
cdc.RegisterConcrete(&MsgUpdateInnerRedemptionRateBounds{}, "stakeibc/UpdateInnerRedemptionRateBounds", nil)
// this line is used by starport scaffolding # 2
}

Expand All @@ -39,6 +40,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
&MsgDeleteValidator{},
&MsgRestoreInterchainAccount{},
&MsgUpdateValidatorSharesExchRate{},
&MsgUpdateInnerRedemptionRateBounds{},
)

registry.RegisterImplementations((*govtypes.Content)(nil),
Expand Down
1 change: 1 addition & 0 deletions x/stakeibc/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ var (
ErrInvalidValidatorDelegationUpdates = errorsmod.Register(ModuleName, 1548, "Invalid validator delegation updates")
ErrLSMLiquidStakeDisabledForHostZone = errorsmod.Register(ModuleName, 1549, "LSM liquid stake is disabled for host zone")
ErrUnableToRemoveValidator = errorsmod.Register(ModuleName, 1550, "Unable to remove validator")
ErrInvalidBounds = errorsmod.Register(ModuleName, 1551, "Invalid safety bounds - inner bounds must be within outer bounds")
)
Loading
Loading