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

added register rebate tx #1138

Merged
merged 12 commits into from
Mar 19, 2024
26 changes: 26 additions & 0 deletions proto/stride/stakeibc/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ service Msg {
returns (MsgDeleteTradeRouteResponse);
rpc UpdateTradeRoute(MsgUpdateTradeRoute)
returns (MsgUpdateTradeRouteResponse);
rpc SetCommunityPoolRebate(MsgSetCommunityPoolRebate)
returns (MsgSetCommunityPoolRebateResponse);
}

message MsgUpdateInnerRedemptionRateBounds {
Expand Down Expand Up @@ -314,3 +316,27 @@ message MsgUpdateTradeRoute {
];
}
message MsgUpdateTradeRouteResponse {}

// Registers or updates a community pool rebate by specifying the amount liquid
// staked
message MsgSetCommunityPoolRebate {
option (cosmos.msg.v1.signer) = "creator";
option (amino.name) = "stride/x/stakeibc/MsgSetCommunityPoolRebate";

// Message signer (admin only)
string creator = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Chain id of the chain whose community pool has a liquid staking rebate
// arrangement with stride
string chain_id = 2;
// Rebate percentage (e.g. 0.5 for 50%)
sampocs marked this conversation as resolved.
Show resolved Hide resolved
string rebate_percentage = 3 [
asalzmann marked this conversation as resolved.
Show resolved Hide resolved
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// Number of native tokens staked by the community pool
string liquid_staked_amount = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
message MsgSetCommunityPoolRebateResponse {}
1 change: 1 addition & 0 deletions x/stakeibc/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(CmdClearBalance())
cmd.AddCommand(CmdUpdateInnerRedemptionRateBounds())
cmd.AddCommand(CmdResumeHostZone())
cmd.AddCommand(CmdSetCommunityPoolRebate())

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

import (
"errors"
"fmt"

sdkmath "cosmossdk.io/math"
"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/v19/x/stakeibc/types"
)

func CmdSetCommunityPoolRebate() *cobra.Command {
cmd := &cobra.Command{
Use: "set-rebate [chain-id] [rebate-percentage] [liquid-staked-amount]",
Short: "Registers or updates a community pool rebate",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) (err error) {
chainId := args[0]
rebatePercentage, err := sdk.NewDecFromStr(args[1])
if err != nil {
return fmt.Errorf("unable to parse rebate percentage: %s", err.Error())
}
liquidStakeAmount, ok := sdkmath.NewIntFromString(args[2])
if !ok {
return errors.New("unable to parse liquid stake amount")
}

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

msg := types.NewMsgSetCommunityPoolRebate(
clientCtx.GetFromAddress().String(),
chainId,
rebatePercentage,
liquidStakeAmount,
)
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 @@ -77,6 +77,9 @@ func NewMessageHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgUpdateTradeRoute:
res, err := msgServer.UpdateTradeRoute(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgSetCommunityPoolRebate:
res, err := msgServer.SetCommunityPoolRebate(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
28 changes: 28 additions & 0 deletions x/stakeibc/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1103,3 +1103,31 @@ func (k msgServer) ResumeHostZone(goCtx context.Context, msg *types.MsgResumeHos

return &types.MsgResumeHostZoneResponse{}, nil
}

// Registers or updates a community pool rebate, configuring the rebate percentage and liquid stake amount
func (k msgServer) SetCommunityPoolRebate(
goCtx context.Context,
msg *types.MsgSetCommunityPoolRebate,
) (*types.MsgSetCommunityPoolRebateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

hostZone, found := k.GetHostZone(ctx, msg.ChainId)
if !found {
return nil, types.ErrHostZoneNotFound.Wrapf("host zone %s not found", msg.ChainId)
}

// If a zero rebate is specified, set the rebate to nil
// Otherwise, update the struct
if msg.LiquidStakedAmount.IsZero() {
shellvish marked this conversation as resolved.
Show resolved Hide resolved
hostZone.CommunityPoolRebate = nil
} else {
hostZone.CommunityPoolRebate = &types.CommunityPoolRebate{
LiquidStakeAmount: msg.LiquidStakedAmount,
RebatePercentage: msg.RebatePercentage,
}
}

k.SetHostZone(ctx, hostZone)

return &types.MsgSetCommunityPoolRebateResponse{}, nil
}
45 changes: 45 additions & 0 deletions x/stakeibc/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2575,3 +2575,48 @@ func (s *KeeperTestSuite) TestResumeHostZone_UnhaltedZones() {
expectedErrorMsg := fmt.Sprintf("invalid chain id, zone for %s not halted: host zone is not halted", HostChainId)
s.Require().Equal(expectedErrorMsg, err.Error(), "should return correct error msg")
}

// ----------------------------------------------------
// SetCommunityPoolRebate
// ----------------------------------------------------

func (s *KeeperTestSuite) TestSetCommunityPoolRebate() {
rebateInfo := types.CommunityPoolRebate{
LiquidStakeAmount: sdk.NewInt(1000),
RebatePercentage: sdk.MustNewDecFromStr("0.5"),
}

// Set host zone with no rebate
hostZone := types.HostZone{
ChainId: HostChainId,
}
s.App.StakeibcKeeper.SetHostZone(s.Ctx, hostZone)

// Submit a message to create the rebate
msg := types.MsgSetCommunityPoolRebate{
ChainId: HostChainId,
RebatePercentage: rebateInfo.RebatePercentage,
LiquidStakedAmount: rebateInfo.LiquidStakeAmount,
}
_, err := s.GetMsgServer().SetCommunityPoolRebate(s.Ctx, &msg)
s.Require().NoError(err, "no error expected when registering rebate")

// Confirm the rebate was updated
actualHostZone := s.MustGetHostZone(HostChainId)
s.Require().Equal(rebateInfo, *actualHostZone.CommunityPoolRebate, "rebate")
shellvish marked this conversation as resolved.
Show resolved Hide resolved

// Submit a 0 LS amount which should delete the rebate
removeMsg := types.MsgSetCommunityPoolRebate{
ChainId: HostChainId,
LiquidStakedAmount: sdk.ZeroInt(),
}
_, err = s.GetMsgServer().SetCommunityPoolRebate(s.Ctx, &removeMsg)
s.Require().NoError(err, "no error expected when registering 0 rebate")

actualHostZone = s.MustGetHostZone(HostChainId)
s.Require().Nil(actualHostZone.CommunityPoolRebate, "rebate")
shellvish marked this conversation as resolved.
Show resolved Hide resolved

// Confirm a message with an invalid chain ID would cause an error
_, err = s.GetMsgServer().SetCommunityPoolRebate(s.Ctx, &types.MsgSetCommunityPoolRebate{ChainId: "invalid"})
s.Require().ErrorContains(err, "host zone not found")
}
5 changes: 2 additions & 3 deletions x/stakeibc/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgCalibrateDelegation{}, "stakeibc/CalibrateDelegation", nil)
cdc.RegisterConcrete(&MsgUpdateInnerRedemptionRateBounds{}, "stakeibc/UpdateInnerRedemptionRateBounds", nil)
cdc.RegisterConcrete(&MsgResumeHostZone{}, "stakeibc/ResumeHostZone", nil)
// this line is used by starport scaffolding # 2
cdc.RegisterConcrete(&MsgSetCommunityPoolRebate{}, "stakeibc/SetCommunityPoolRebate", nil)
sampocs marked this conversation as resolved.
Show resolved Hide resolved
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand All @@ -45,15 +45,14 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
&MsgCalibrateDelegation{},
&MsgUpdateInnerRedemptionRateBounds{},
&MsgResumeHostZone{},
&MsgSetCommunityPoolRebate{},
)

registry.RegisterImplementations((*govtypes.Content)(nil),
&AddValidatorsProposal{},
&ToggleLSMProposal{},
)

// this line is used by starport scaffolding # 3

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}

Expand Down
73 changes: 73 additions & 0 deletions x/stakeibc/types/message_set_community_pool_rebate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package types

import (
"errors"

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

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/Stride-Labs/stride/v19/utils"
)

const TypeMsgSetCommunityPoolRebate = "register_community_pool_rebate"

var _ sdk.Msg = &MsgSetCommunityPoolRebate{}

func NewMsgSetCommunityPoolRebate(
creator string,
chainId string,
rebatePercentage sdk.Dec,
liquidStakedAmount sdkmath.Int,
) *MsgSetCommunityPoolRebate {
return &MsgSetCommunityPoolRebate{
Creator: creator,
ChainId: chainId,
RebatePercentage: rebatePercentage,
LiquidStakedAmount: liquidStakedAmount,
}
}

func (msg *MsgSetCommunityPoolRebate) Route() string {
return RouterKey
}

func (msg *MsgSetCommunityPoolRebate) Type() string {
return TypeMsgSetCommunityPoolRebate
}

func (msg *MsgSetCommunityPoolRebate) GetSigners() []sdk.AccAddress {
creator, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
panic(err)
}
return []sdk.AccAddress{creator}
}

func (msg *MsgSetCommunityPoolRebate) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}

func (msg *MsgSetCommunityPoolRebate) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err)
}
if err := utils.ValidateAdminAddress(msg.Creator); err != nil {
return err
}
if msg.ChainId == "" {
return errors.New("chain ID must be specified")
}
if msg.RebatePercentage.IsNil() || msg.RebatePercentage.LT(sdk.ZeroDec()) || msg.RebatePercentage.GT(sdk.OneDec()) {
return errors.New("invalid rebate percentage, must be between 0 and 1 (inclusive)")
}
if msg.LiquidStakedAmount.IsNil() || msg.LiquidStakedAmount.LT(sdkmath.ZeroInt()) {
return errors.New("invalid liquid stake amount, must be greater than or equal to zero")
}

return nil
}
Loading