Separate multipliers per claimed denom (#971)

* move multipliers to their own file

* add multipliers per denom to MsgClaimHardReward

* claim with multipliers per denom for hard claims

* remove unused error

* add multipliers per denom to params
connect to to hard claiming

* temporary fix migration

* update usdx claiming for new multiplier params

* claim with multipliers per denom for delegator

* claim with multipliers per denom for swap rewards

* tidy up multiplier name validation

* rename new multipliers field in params

* remove old multpliers from params

* clear up various TODOs

* add tags to new structs

* remove dead code
This commit is contained in:
Ruaridh 2021-08-04 15:55:29 +01:00 committed by GitHub
parent 6bcc843c2e
commit 63c8421a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 974 additions and 773 deletions

View File

@ -15,6 +15,20 @@ func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisSt
newMultiplier := v0_15incentive.NewMultiplier(v0_15incentive.MultiplierName(m.Name), m.MonthsLockup, m.Factor) newMultiplier := v0_15incentive.NewMultiplier(v0_15incentive.MultiplierName(m.Name), m.MonthsLockup, m.Factor)
claimMultipliers = append(claimMultipliers, newMultiplier) claimMultipliers = append(claimMultipliers, newMultiplier)
} }
newMultipliers := v0_15incentive.MultipliersPerDenom{
{
Denom: "hard",
Multipliers: claimMultipliers,
},
{
Denom: "ukava",
Multipliers: claimMultipliers,
},
{
Denom: "swp",
Multipliers: claimMultipliers, // TODO set the correct multipliers
},
}
usdxMintingRewardPeriods := v0_15incentive.RewardPeriods{} usdxMintingRewardPeriods := v0_15incentive.RewardPeriods{}
for _, rp := range incentiveGS.Params.USDXMintingRewardPeriods { for _, rp := range incentiveGS.Params.USDXMintingRewardPeriods {
@ -41,7 +55,7 @@ func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisSt
migrateMultiRewardPeriods(incentiveGS.Params.HardBorrowRewardPeriods), migrateMultiRewardPeriods(incentiveGS.Params.HardBorrowRewardPeriods),
hardDelegatorRewardPeriods, hardDelegatorRewardPeriods,
swapRewardPeriods, swapRewardPeriods,
claimMultipliers, newMultipliers,
incentiveGS.Params.ClaimEnd, incentiveGS.Params.ClaimEnd,
) )

View File

@ -52,9 +52,7 @@ var (
NewQuerier = keeper.NewQuerier NewQuerier = keeper.NewQuerier
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams DefaultParams = types.DefaultParams
FilterCoins = types.FilterCoins
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
MultiplyCoins = types.MultiplyCoins
NewAccumulationTime = types.NewAccumulationTime NewAccumulationTime = types.NewAccumulationTime
NewAccumulator = types.NewAccumulator NewAccumulator = types.NewAccumulator
NewDelegatorClaim = types.NewDelegatorClaim NewDelegatorClaim = types.NewDelegatorClaim
@ -79,9 +77,12 @@ var (
NewQueryRewardsParams = types.NewQueryRewardsParams NewQueryRewardsParams = types.NewQueryRewardsParams
NewRewardIndex = types.NewRewardIndex NewRewardIndex = types.NewRewardIndex
NewRewardPeriod = types.NewRewardPeriod NewRewardPeriod = types.NewRewardPeriod
NewSelection = types.NewSelection
NewSelectionsFromMap = types.NewSelectionsFromMap
NewSwapClaim = types.NewSwapClaim NewSwapClaim = types.NewSwapClaim
NewUSDXMintingClaim = types.NewUSDXMintingClaim NewUSDXMintingClaim = types.NewUSDXMintingClaim
ParamKeyTable = types.ParamKeyTable ParamKeyTable = types.ParamKeyTable
ParseMultiplierName = types.ParseMultiplierName
RegisterCodec = types.RegisterCodec RegisterCodec = types.RegisterCodec
// variable aliases // variable aliases
@ -105,7 +106,6 @@ var (
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
ErrInvalidAccountType = types.ErrInvalidAccountType ErrInvalidAccountType = types.ErrInvalidAccountType
ErrInvalidClaimDenoms = types.ErrInvalidClaimDenoms ErrInvalidClaimDenoms = types.ErrInvalidClaimDenoms
ErrInvalidClaimOwner = types.ErrInvalidClaimOwner
ErrInvalidClaimType = types.ErrInvalidClaimType ErrInvalidClaimType = types.ErrInvalidClaimType
ErrInvalidMultiplier = types.ErrInvalidMultiplier ErrInvalidMultiplier = types.ErrInvalidMultiplier
ErrNoClaimsFound = types.ErrNoClaimsFound ErrNoClaimsFound = types.ErrNoClaimsFound
@ -171,6 +171,7 @@ type (
Multiplier = types.Multiplier Multiplier = types.Multiplier
MultiplierName = types.MultiplierName MultiplierName = types.MultiplierName
Multipliers = types.Multipliers Multipliers = types.Multipliers
MultipliersPerDenom = types.MultipliersPerDenom
ParamSubspace = types.ParamSubspace ParamSubspace = types.ParamSubspace
Params = types.Params Params = types.Params
QueryGetRewardFactorsResponse = types.QueryGetRewardFactorsResponse QueryGetRewardFactorsResponse = types.QueryGetRewardFactorsResponse
@ -179,6 +180,8 @@ type (
RewardIndexes = types.RewardIndexes RewardIndexes = types.RewardIndexes
RewardPeriod = types.RewardPeriod RewardPeriod = types.RewardPeriod
RewardPeriods = types.RewardPeriods RewardPeriods = types.RewardPeriods
Selection = types.Selection
Selections = types.Selections
StakingKeeper = types.StakingKeeper StakingKeeper = types.StakingKeeper
SupplyKeeper = types.SupplyKeeper SupplyKeeper = types.SupplyKeeper
SwapClaim = types.SwapClaim SwapClaim = types.SwapClaim

View File

@ -18,6 +18,9 @@ import (
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
const multiplierFlag = "multiplier"
const multiplierFlagShort = "m"
// GetTxCmd returns the transaction cli commands for the incentive module // GetTxCmd returns the transaction cli commands for the incentive module
func GetTxCmd(cdc *codec.Codec) *cobra.Command { func GetTxCmd(cdc *codec.Codec) *cobra.Command {
incentiveTxCmd := &cobra.Command{ incentiveTxCmd := &cobra.Command{
@ -100,84 +103,118 @@ A receiver address for the rewards is needed as validator vesting accounts canno
} }
func getCmdClaimHard(cdc *codec.Codec) *cobra.Command { func getCmdClaimHard(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string var denomsToClaim map[string]string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "claim-hard [multiplier]", Use: "claim-hard",
Short: "claim sender's Hard module rewards using a given multiplier", Short: "claim sender's Hard module rewards using given multipliers",
Long: `Claim sender's outstanding Hard rewards for deposit/borrow using given multiplier`, Long: `Claim sender's outstanding Hard rewards for deposit/borrow using given multipliers`,
Example: fmt.Sprintf(` $ %s tx %s claim-hard large`, version.ClientName, types.ModuleName), Example: strings.Join([]string{
Args: cobra.ExactArgs(1), fmt.Sprintf(` $ %s tx %s claim-hard --%s hard=large --%s ukava=small`, version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(` $ %s tx %s claim-hard --%s hard=large,ukava=small`, version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin()) inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress() sender := cliCtx.GetFromAddress()
multiplier := args[0] selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimHardReward(sender, multiplier, denomsToClaim) msg := types.NewMsgClaimHardReward(sender, selections...)
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms") cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd return cmd
} }
func getCmdClaimHardVVesting(cdc *codec.Codec) *cobra.Command { func getCmdClaimHardVVesting(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string var denomsToClaim map[string]string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "claim-hard-vesting [multiplier] [receiver]", Use: "claim-hard-vesting [receiver]",
Short: "claim Hard module rewards on behalf of a validator vesting account using a given multiplier", Short: "claim Hard module rewards on behalf of a validator vesting account using given multipliers",
Long: `Claim sender's outstanding hard supply/borrow rewards on behalf of a validator vesting account using given multiplier Long: `Claim sender's outstanding hard supply/borrow rewards on behalf of a validator vesting account using given multipliers
A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens. A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.`,
Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
Example: strings.Join([]string{ Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-hard-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw", version.ClientName, types.ModuleName), fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s hard=large --%s ukava=small", version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(" $ %s tx %s claim-hard-vesting small kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --claim-only swp,hard", version.ClientName, types.ModuleName), fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s hard=large,ukava=small", version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"), }, "\n"),
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin()) inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress() sender := cliCtx.GetFromAddress()
multiplier := args[0] receiver, err := sdk.AccAddressFromBech32(args[1])
receiverStr := args[1]
receiver, err := sdk.AccAddressFromBech32(receiverStr)
if err != nil { if err != nil {
return err return err
} }
selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimHardRewardVVesting(sender, receiver, multiplier, denomsToClaim) msg := types.NewMsgClaimHardRewardVVesting(sender, receiver, selections...)
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms") cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd return cmd
} }
func getCmdClaimDelegator(cdc *codec.Codec) *cobra.Command { func getCmdClaimDelegator(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string var denomsToClaim map[string]string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "claim-delegator [multiplier]", Use: "claim-delegator",
Short: "claim sender's delegator rewards using a given multiplier", Short: "claim sender's non-sdk delegator rewards using given multipliers",
Long: `Claim sender's outstanding delegator rewards using given multiplier. Long: `Claim sender's outstanding delegator rewards using given multipliers`,
Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
Example: strings.Join([]string{ Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-delegator large", version.ClientName, types.ModuleName), fmt.Sprintf(` $ %s tx %s claim-delegator --%s hard=large --%s swp=small`, version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(" $ %s tx %s claim-delegator large --claim-only swp,hard", version.ClientName, types.ModuleName), fmt.Sprintf(` $ %s tx %s claim-delegator --%s hard=large,swp=small`, version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress()
selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimDelegatorReward(sender, selections...)
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd
}
func getCmdClaimDelegatorVVesting(cdc *codec.Codec) *cobra.Command {
var denomsToClaim map[string]string
cmd := &cobra.Command{
Use: "claim-delegator-vesting [receiver]",
Short: "claim non-sdk delegator rewards on behalf of a validator vesting account using given multipliers",
Long: `Claim sender's outstanding delegator rewards on behalf of a validator vesting account using given multipliers
A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.`,
Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s hard=large --%s swp=small", version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s hard=large,swp=small", version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"), }, "\n"),
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@ -186,70 +223,68 @@ Optionally claim only certain denoms from the rewards. Specifying none will clai
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress() sender := cliCtx.GetFromAddress()
multiplier := args[0] receiver, err := sdk.AccAddressFromBech32(args[1])
msg := types.NewMsgClaimDelegatorReward(sender, multiplier, denomsToClaim)
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms")
return cmd
}
func getCmdClaimDelegatorVVesting(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string
cmd := &cobra.Command{
Use: "claim-delegator-vesting [multiplier] [receiver]",
Short: "claim delegator rewards on behalf of a validator vesting account using a given multiplier",
Long: `Claim sender's outstanding delegator rewards on behalf of a validator vesting account using given multiplier
A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.
Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-delegator-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw", version.ClientName, types.ModuleName),
fmt.Sprintf(" $ %s tx %s claim-delegator-vesting small kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --claim-only swp,hard", version.ClientName, types.ModuleName),
}, "\n"),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress()
multiplier := args[0]
receiverStr := args[1]
receiver, err := sdk.AccAddressFromBech32(receiverStr)
if err != nil { if err != nil {
return err return err
} }
selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimDelegatorRewardVVesting(sender, receiver, multiplier, denomsToClaim) msg := types.NewMsgClaimDelegatorRewardVVesting(sender, receiver, selections...)
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms") cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd return cmd
} }
func getCmdClaimSwap(cdc *codec.Codec) *cobra.Command { func getCmdClaimSwap(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string var denomsToClaim map[string]string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "claim-swap [multiplier]", Use: "claim-swap",
Short: "claim sender's swap rewards using a given multiplier", Short: "claim sender's swap rewards using given multipliers",
Long: `Claim sender's outstanding swap rewards using given multiplier. Long: `Claim sender's outstanding swap rewards using given multipliers`,
Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
Example: strings.Join([]string{ Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-swap large", version.ClientName, types.ModuleName), fmt.Sprintf(` $ %s tx %s claim-hard --%s swp=large --%s ukava=small`, version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(" $ %s tx %s claim-swap large --claim-only swp,hard", version.ClientName, types.ModuleName), fmt.Sprintf(` $ %s tx %s claim-hard --%s swp=large,ukava=small`, version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress()
selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimSwapReward(sender, selections...)
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd
}
func getCmdClaimSwapVVesting(cdc *codec.Codec) *cobra.Command {
var denomsToClaim map[string]string
cmd := &cobra.Command{
Use: "claim-swap-vesting [receiver]",
Short: "claim swap rewards on behalf of a validator vesting account using given multipliers",
Long: `Claim sender's outstanding swap rewards on behalf of a validator vesting account using given multipliers
A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.`,
Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s ukava=large --%s swp=small", version.ClientName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(" $ %s tx %s claim-hard-vesting kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --%s ukava=large,swp=small", version.ClientName, types.ModuleName, multiplierFlag),
}, "\n"), }, "\n"),
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@ -258,55 +293,21 @@ Optionally claim only certain denoms from the rewards. Specifying none will clai
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress() sender := cliCtx.GetFromAddress()
multiplier := args[0] receiver, err := sdk.AccAddressFromBech32(args[1])
msg := types.NewMsgClaimSwapReward(sender, multiplier, denomsToClaim)
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms")
return cmd
}
func getCmdClaimSwapVVesting(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string
cmd := &cobra.Command{
Use: "claim-swap-vesting [multiplier] [receiver]",
Short: "claim swap rewards on behalf of a validator vesting account using a given multiplier",
Long: `Claim sender's outstanding swap rewards on behalf of a validator vesting account using given multiplier
A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.
Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
Example: strings.Join([]string{
fmt.Sprintf(" $ %s tx %s claim-swap-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw", version.ClientName, types.ModuleName),
fmt.Sprintf(" $ %s tx %s claim-swap-vesting small kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --claim-only swp,hard", version.ClientName, types.ModuleName),
}, "\n"),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
sender := cliCtx.GetFromAddress()
multiplier := args[0]
receiverStr := args[1]
receiver, err := sdk.AccAddressFromBech32(receiverStr)
if err != nil { if err != nil {
return err return err
} }
selections := types.NewSelectionsFromMap(denomsToClaim)
msg := types.NewMsgClaimSwapRewardVVesting(sender, receiver, multiplier, denomsToClaim) msg := types.NewMsgClaimSwapRewardVVesting(sender, receiver, selections...)
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
cmd.Flags().StringSliceVar(&denomsToClaim, "claim-only", nil, "claim only these denoms, otherwise claim all denoms") cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
cmd.MarkFlagRequired(multiplierFlag)
return cmd return cmd
} }

View File

@ -6,6 +6,8 @@ import (
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/types/rest"
"github.com/kava-labs/kava/x/incentive/types"
) )
// REST variable names // REST variable names
@ -22,17 +24,15 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
// PostClaimReq defines the properties of claim transaction's request body. // PostClaimReq defines the properties of claim transaction's request body.
type PostClaimReq struct { type PostClaimReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim types.Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// PostClaimReq defines the properties of claim transaction's request body. // PostClaimReq defines the properties of claim transaction's request body.
type PostClaimVVestingReq struct { type PostClaimVVestingReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim types.Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }

View File

@ -29,20 +29,23 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/incentive/claim-swap-vesting", postClaimVVestingHandlerFn(cliCtx, swapVVGenerator)).Methods("POST") r.HandleFunc("/incentive/claim-swap-vesting", postClaimVVestingHandlerFn(cliCtx, swapVVGenerator)).Methods("POST")
} }
func usdxMintingGenerator(req PostClaimReq) sdk.Msg { func usdxMintingGenerator(req PostClaimReq) (sdk.Msg, error) {
return types.NewMsgClaimUSDXMintingReward(req.Sender, req.MultiplierName) if len(req.DenomsToClaim) != 1 {
return nil, fmt.Errorf("must only claim %s denom for usdx minting rewards, got '%s", types.USDXMintingRewardDenom, req.DenomsToClaim)
}
return types.NewMsgClaimUSDXMintingReward(req.Sender, req.DenomsToClaim[0].MultiplierName), nil
} }
func hardGenerator(req PostClaimReq) sdk.Msg { func hardGenerator(req PostClaimReq) (sdk.Msg, error) {
return types.NewMsgClaimHardReward(req.Sender, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimHardReward(req.Sender, req.DenomsToClaim...), nil
} }
func delegatorGenerator(req PostClaimReq) sdk.Msg { func delegatorGenerator(req PostClaimReq) (sdk.Msg, error) {
return types.NewMsgClaimDelegatorReward(req.Sender, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimDelegatorReward(req.Sender, req.DenomsToClaim...), nil
} }
func swapGenerator(req PostClaimReq) sdk.Msg { func swapGenerator(req PostClaimReq) (sdk.Msg, error) {
return types.NewMsgClaimSwapReward(req.Sender, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimSwapReward(req.Sender, req.DenomsToClaim...), nil
} }
func postClaimHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimReq) sdk.Msg) http.HandlerFunc { func postClaimHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimReq) (sdk.Msg, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimReq var requestBody PostClaimReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) { if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
@ -65,7 +68,12 @@ func postClaimHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostCla
return return
} }
msg := msgGenerator(requestBody) msg, err := msgGenerator(requestBody)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
@ -75,20 +83,23 @@ func postClaimHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostCla
} }
} }
func usdxMintingVVGenerator(req PostClaimVVestingReq) sdk.Msg { func usdxMintingVVGenerator(req PostClaimVVestingReq) (sdk.Msg, error) {
return types.NewMsgClaimUSDXMintingRewardVVesting(req.Sender, req.Receiver, req.MultiplierName) if len(req.DenomsToClaim) != 1 {
return nil, fmt.Errorf("must only claim %s denom for usdx minting rewards, got '%s", types.USDXMintingRewardDenom, req.DenomsToClaim)
}
return types.NewMsgClaimUSDXMintingRewardVVesting(req.Sender, req.Receiver, req.DenomsToClaim[0].MultiplierName), nil
} }
func hardVVGenerator(req PostClaimVVestingReq) sdk.Msg { func hardVVGenerator(req PostClaimVVestingReq) (sdk.Msg, error) {
return types.NewMsgClaimHardRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimHardRewardVVesting(req.Sender, req.Receiver, req.DenomsToClaim...), nil
} }
func delegatorVVGenerator(req PostClaimVVestingReq) sdk.Msg { func delegatorVVGenerator(req PostClaimVVestingReq) (sdk.Msg, error) {
return types.NewMsgClaimDelegatorRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimDelegatorRewardVVesting(req.Sender, req.Receiver, req.DenomsToClaim...), nil
} }
func swapVVGenerator(req PostClaimVVestingReq) sdk.Msg { func swapVVGenerator(req PostClaimVVestingReq) (sdk.Msg, error) {
return types.NewMsgClaimSwapRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim) return types.NewMsgClaimSwapRewardVVesting(req.Sender, req.Receiver, req.DenomsToClaim...), nil
} }
func postClaimVVestingHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimVVestingReq) sdk.Msg) http.HandlerFunc { func postClaimVVestingHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimVVestingReq) (sdk.Msg, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimVVestingReq var requestBody PostClaimVVestingReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) { if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
@ -111,7 +122,11 @@ func postClaimVVestingHandlerFn(cliCtx context.CLIContext, msgGenerator func(req
return return
} }
msg := msgGenerator(requestBody) msg, err := msgGenerator(requestBody)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if err := msg.ValidateBasic(); err != nil { if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return

View File

@ -65,8 +65,29 @@ func (suite *GenesisTestSuite) SetupTest() {
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "ukava", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "ukava", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "btcb/usdx", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "btcb/usdx", suite.genesisTime.Add(-1*oneYear), suite.genesisTime.Add(oneYear), cs(c("swp", 122354)))},
incentive.Multipliers{incentive.NewMultiplier(incentive.Small, 1, d("0.25")), incentive.NewMultiplier(incentive.Large, 12, d("1.0"))}, incentive.MultipliersPerDenom{
{
Denom: "ukava",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
},
{
Denom: "hard",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
},
{
Denom: "swp",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Medium, 6, d("0.8")),
},
},
},
suite.genesisTime.Add(5*oneYear), suite.genesisTime.Add(5*oneYear),
), ),
incentive.DefaultGenesisRewardState, incentive.DefaultGenesisRewardState,
@ -104,8 +125,29 @@ func (suite *GenesisTestSuite) TestExportedGenesisMatchesImported() {
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "ukava", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "ukava", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("hard", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "btcb/usdx", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("swap", 122354)))}, incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "btcb/usdx", genesisTime.Add(-1*oneYear), genesisTime.Add(oneYear), cs(c("swp", 122354)))},
incentive.Multipliers{incentive.NewMultiplier(incentive.Small, 1, d("0.25")), incentive.NewMultiplier(incentive.Large, 12, d("1.0"))}, incentive.MultipliersPerDenom{
{
Denom: "ukava",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
},
{
Denom: "hard",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
},
{
Denom: "swp",
Multipliers: incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Medium, 6, d("0.8")),
},
},
},
genesisTime.Add(5*oneYear), genesisTime.Add(5*oneYear),
), ),
incentive.NewGenesisRewardState( incentive.NewGenesisRewardState(

View File

@ -36,7 +36,7 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
} }
func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingReward) (*sdk.Result, error) { func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingReward) (*sdk.Result, error) {
err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName)) err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Sender, msg.MultiplierName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -50,7 +50,7 @@ func handleMsgClaimUSDXMintingRewardVVesting(ctx sdk.Context, k keeper.Keeper, m
if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil { if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err return nil, err
} }
err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName)) err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Receiver, msg.MultiplierName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -60,10 +60,12 @@ func handleMsgClaimUSDXMintingRewardVVesting(ctx sdk.Context, k keeper.Keeper, m
} }
func handleMsgClaimHardReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimHardReward) (*sdk.Result, error) { func handleMsgClaimHardReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimHardReward) (*sdk.Result, error) {
for _, selection := range msg.DenomsToClaim {
err := k.ClaimHardReward(ctx, msg.Sender, msg.Sender, selection.Denom, selection.MultiplierName)
if err != nil {
return nil, err
}
err := k.ClaimHardReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil {
return nil, err
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
@ -75,9 +77,12 @@ func handleMsgClaimHardRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg type
if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil { if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err return nil, err
} }
err := k.ClaimHardReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim) for _, selection := range msg.DenomsToClaim {
if err != nil { err := k.ClaimHardReward(ctx, msg.Sender, msg.Receiver, selection.Denom, selection.MultiplierName)
return nil, err if err != nil {
return nil, err
}
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
@ -86,9 +91,11 @@ func handleMsgClaimHardRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg type
func handleMsgClaimDelegatorReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimDelegatorReward) (*sdk.Result, error) { func handleMsgClaimDelegatorReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimDelegatorReward) (*sdk.Result, error) {
err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim) for _, selection := range msg.DenomsToClaim {
if err != nil { err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Sender, selection.Denom, selection.MultiplierName)
return nil, err if err != nil {
return nil, err
}
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
@ -100,9 +107,11 @@ func handleMsgClaimDelegatorRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg
if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil { if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err return nil, err
} }
err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim) for _, selection := range msg.DenomsToClaim {
if err != nil { err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Receiver, selection.Denom, selection.MultiplierName)
return nil, err if err != nil {
return nil, err
}
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
@ -111,9 +120,11 @@ func handleMsgClaimDelegatorRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg
func handleMsgClaimSwapReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimSwapReward) (*sdk.Result, error) { func handleMsgClaimSwapReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimSwapReward) (*sdk.Result, error) {
err := k.ClaimSwapReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim) for _, selection := range msg.DenomsToClaim {
if err != nil { err := k.ClaimSwapReward(ctx, msg.Sender, msg.Sender, selection.Denom, selection.MultiplierName)
return nil, err if err != nil {
return nil, err
}
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
@ -125,9 +136,11 @@ func handleMsgClaimSwapRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg type
if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil { if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err return nil, err
} }
err := k.ClaimSwapReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim) for _, selection := range msg.DenomsToClaim {
if err != nil { err := k.ClaimSwapReward(ctx, msg.Sender, msg.Receiver, selection.Denom, selection.MultiplierName)
return nil, err if err != nil {
return nil, err
}
} }
return &sdk.Result{ return &sdk.Result{
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),

View File

@ -9,7 +9,7 @@ import (
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
func (suite *HandlerTestSuite) TestPayoutDelegatorClaim() { func (suite *HandlerTestSuite) TestPayoutDelegatorClaimMultiDenom() {
userAddr := suite.addrs[0] userAddr := suite.addrs[0]
receiverAddr := suite.addrs[1] receiverAddr := suite.addrs[1]
@ -18,7 +18,7 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaim() {
WithSimpleAccount(receiverAddr, nil) WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder(). incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6))) WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder) suite.SetupWithGenState(authBulder, incentBuilder)
@ -35,22 +35,24 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaim() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(userAddr, receiverAddr, "large", nil), types.NewMsgClaimDelegatorRewardVVesting(userAddr, receiverAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim a single denom // Claim denoms
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(userAddr, "large", nil), types.NewMsgClaimDelegatorReward(userAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := cs(c("hard", 2*7*1e6)) expectedRewardsHard := c("hard", int64(0.2*float64(2*7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...)) expectedRewardsSwap := c("swap", int64(0.5*float64(2*7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004786, Amount: expectedRewards}, {Length: (17+31)*secondsPerDay - 2*7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.DelegatorRewardEquals(userAddr, nil) suite.DelegatorRewardEquals(userAddr, nil)
@ -80,13 +82,13 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimSingleDenom() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(userAddr, suite.addrs[1], "large", nil), types.NewMsgClaimDelegatorRewardVVesting(userAddr, suite.addrs[1], types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(userAddr, "large", []string{"swap"}), types.NewMsgClaimDelegatorReward(userAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -95,14 +97,14 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimSingleDenom() {
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 2*7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.DelegatorRewardEquals(userAddr, cs(c("hard", 2*7*1e6))) suite.DelegatorRewardEquals(userAddr, cs(c("hard", 2*7*1e6)))
} }
func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVesting() { func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVestingMultiDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1] valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12))) vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12)))
@ -112,7 +114,7 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVesting() {
WithSimpleAccount(receiverAddr, nil) WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder(). incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6))) WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder) suite.SetupWithGenState(authBulder, incentBuilder)
@ -129,22 +131,24 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVesting() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(valAddr, "large", nil), types.NewMsgClaimDelegatorReward(valAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, "large", nil), types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := c("hard", 2*7*1e6) expectedRewardsHard := c("hard", int64(0.2*float64(2*7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) expectedRewardsSwap := c("swap", int64(0.5*float64(2*7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)}, {Length: (17+31)*secondsPerDay - 2*7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that each claim reward coin's amount has been reset to 0 // Check that each claim reward coin's amount has been reset to 0
@ -178,13 +182,13 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVestingSingleDenom() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(valAddr, "large", []string{"swap"}), types.NewMsgClaimDelegatorReward(valAddr, types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}), types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -193,7 +197,7 @@ func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVestingSingleDenom() {
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 2*7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward

View File

@ -8,7 +8,7 @@ import (
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
func (suite *HandlerTestSuite) TestPayoutHardClaim() { func (suite *HandlerTestSuite) TestPayoutHardClaimMultiDenom() {
userAddr, receiverAddr := suite.addrs[0], suite.addrs[1] userAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
authBulder := suite.authBuilder(). authBulder := suite.authBuilder().
@ -16,8 +16,8 @@ func (suite *HandlerTestSuite) TestPayoutHardClaim() {
WithSimpleAccount(receiverAddr, nil) WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder(). incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6))). WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6))) WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder) suite.SetupWithGenState(authBulder, incentBuilder)
@ -32,22 +32,24 @@ func (suite *HandlerTestSuite) TestPayoutHardClaim() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(userAddr, receiverAddr, "large", nil), types.NewMsgClaimHardRewardVVesting(userAddr, receiverAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim a single denom // Claim denoms
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(userAddr, "large", nil), types.NewMsgClaimHardReward(userAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := cs(c("hard", 2*7*1e6)) expectedRewardsHard := c("hard", int64(0.2*float64(2*7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...)) expectedRewardsSwap := c("swap", int64(0.5*float64(2*7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards}, {Length: (17+31)*secondsPerDay - 7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.HardRewardEquals(userAddr, nil) suite.HardRewardEquals(userAddr, nil)
@ -76,13 +78,13 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimSingleDenom() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(userAddr, suite.addrs[1], "large", []string{"swap"}), types.NewMsgClaimHardRewardVVesting(userAddr, suite.addrs[1], types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(userAddr, "large", []string{"swap"}), types.NewMsgClaimHardReward(userAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -91,14 +93,14 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimSingleDenom() {
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.HardRewardEquals(userAddr, cs(c("hard", 2*7*1e6))) suite.HardRewardEquals(userAddr, cs(c("hard", 2*7*1e6)))
} }
func (suite *HandlerTestSuite) TestPayoutHardClaimVVesting() { func (suite *HandlerTestSuite) TestPayoutHardClaimVVestingMultiDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1] valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("bnb", 1e12))) vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("bnb", 1e12)))
@ -108,8 +110,8 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimVVesting() {
WithSimpleAccount(receiverAddr, nil) WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder(). incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6))). WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6))) WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder) suite.SetupWithGenState(authBulder, incentBuilder)
@ -124,22 +126,24 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimVVesting() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(valAddr, "large", nil), types.NewMsgClaimHardReward(valAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, "large", nil), types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := c("hard", 2*7*1e6) expectedRewardsHard := c("hard", int64(0.2*float64(2*7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) expectedRewardsSwap := c("swap", int64(0.5*float64(2*7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31)*secondsPerDay - 7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that each claim reward coin's amount has been reset to 0 // Check that each claim reward coin's amount has been reset to 0
@ -172,13 +176,13 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimVVestingSingleDenom() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(valAddr, "large", []string{"swap"}), types.NewMsgClaimHardReward(valAddr, types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}), types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -187,7 +191,7 @@ func (suite *HandlerTestSuite) TestPayoutHardClaimVVestingSingleDenom() {
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward

View File

@ -11,26 +11,18 @@ import (
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
"github.com/kava-labs/kava/x/incentive"
"github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/testutil" "github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist" "github.com/kava-labs/kava/x/kavadist"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting" validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
) )
const secondsPerDay = 24 * 60 * 60
// Test suite used for all keeper tests // Test suite used for all keeper tests
type HandlerTestSuite struct { type HandlerTestSuite struct {
testutil.IntegrationTester testutil.IntegrationTester
// TODO remove these
keeper keeper.Keeper
hardKeeper hardkeeper.Keeper
cdpKeeper cdpkeeper.Keeper
handler sdk.Handler
genesisTime time.Time genesisTime time.Time
addrs []sdk.AccAddress addrs []sdk.AccAddress
} }
@ -52,11 +44,6 @@ func (suite *HandlerTestSuite) SetupTest() {
func (suite *HandlerTestSuite) SetupApp() { func (suite *HandlerTestSuite) SetupApp() {
suite.App = app.NewTestApp() suite.App = app.NewTestApp()
suite.keeper = suite.App.GetIncentiveKeeper()
suite.hardKeeper = suite.App.GetHardKeeper()
suite.cdpKeeper = suite.App.GetCDPKeeper()
suite.handler = incentive.NewHandler(suite.keeper)
suite.Ctx = suite.App.NewContext(true, abci.Header{Height: 1, Time: suite.genesisTime}) suite.Ctx = suite.App.NewContext(true, abci.Header{Height: 1, Time: suite.genesisTime})
} }
@ -106,13 +93,32 @@ func (suite *HandlerTestSuite) authBuilder() app.AuthGenesisBuilder {
func (suite *HandlerTestSuite) incentiveBuilder() testutil.IncentiveGenesisBuilder { func (suite *HandlerTestSuite) incentiveBuilder() testutil.IncentiveGenesisBuilder {
return testutil.NewIncentiveGenesisBuilder(). return testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithMultipliers(types.Multipliers{ WithMultipliers(types.MultipliersPerDenom{
types.NewMultiplier(types.MultiplierName("small"), 1, d("0.2")), {
types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")), Denom: "hard",
Multipliers: types.Multipliers{
types.NewMultiplier(types.MultiplierName("small"), 1, d("0.2")),
types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")),
},
},
{
Denom: "swap",
Multipliers: types.Multipliers{
types.NewMultiplier(types.MultiplierName("medium"), 6, d("0.5")),
types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")),
},
},
{
Denom: "ukava",
Multipliers: types.Multipliers{
types.NewMultiplier(types.MultiplierName("small"), 1, d("0.2")),
types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")),
},
},
}) })
} }
func (suite *HandlerTestSuite) TestPayoutSwapClaim() { func (suite *HandlerTestSuite) TestPayoutSwapClaimMultiDenom() {
userAddr := suite.addrs[0] userAddr := suite.addrs[0]
authBulder := suite.authBuilder(). authBulder := suite.authBuilder().
@ -134,22 +140,24 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaim() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], "large", nil), types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(userAddr, "large", nil), types.NewMsgClaimSwapReward(userAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := cs(c("swap", 7*1e6), c("hard", 7*1e6)) expectedRewardsHard := c("hard", int64(0.2*float64(7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...)) expectedRewardsSwap := c("swap", int64(0.5*float64(7*1e6)))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards}, {Length: (17+31)*secondsPerDay - 7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that each claim reward coin's amount has been reset to 0 // Check that each claim reward coin's amount has been reset to 0
@ -179,13 +187,13 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaimSingleDenom() {
// Check rewards cannot be claimed by vvesting claim msgs // Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], "large", nil), types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(userAddr, "large", []string{"swap"}), types.NewMsgClaimSwapReward(userAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -194,14 +202,14 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaimSingleDenom() {
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.SwapRewardEquals(userAddr, cs(c("hard", 7*1e6))) suite.SwapRewardEquals(userAddr, cs(c("hard", 7*1e6)))
} }
func (suite *HandlerTestSuite) TestPayoutSwapClaimVVesting() { func (suite *HandlerTestSuite) TestPayoutSwapClaimVVestingMultiDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1] valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12), c("busd", 1e12))) vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12), c("busd", 1e12)))
@ -227,22 +235,24 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaimVVesting() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(valAddr, "large", nil), types.NewMsgClaimSwapReward(valAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, "large", nil), types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, types.NewSelection("hard", "small"), types.NewSelection("swap", "medium")),
) )
suite.NoError(err) suite.NoError(err)
// Check rewards were paid out // Check rewards were paid out
expectedRewards := cs(c("hard", 7*1e6), c("swap", 7*1e6)) expectedRewardsHard := c("hard", int64(0.2*float64(7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards...)) expectedRewardsSwap := c("swap", int64(0.5*float64(7*1e6)))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewardsHard, expectedRewardsSwap))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards}, {Length: (17+31)*secondsPerDay - 7, Amount: cs(expectedRewardsHard)},
{Length: (28 + 31 + 30 + 31 + 30) * secondsPerDay, Amount: cs(expectedRewardsSwap)}, // second length is stacked on top of the first
}) })
// Check that each claim reward coin's amount has been reset to 0 // Check that each claim reward coin's amount has been reset to 0
@ -275,13 +285,13 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaimVVestingSingleDenom() {
// Check rewards cannot be claimed by normal claim msgs // Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg( err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(valAddr, "large", []string{"swap"}), types.NewMsgClaimSwapReward(valAddr, types.NewSelection("swap", "large")),
) )
suite.ErrorIs(err, types.ErrInvalidAccountType) suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards // Claim rewards
err = suite.DeliverIncentiveMsg( err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}), types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, types.NewSelection("swap", "large")),
) )
suite.NoError(err) suite.NoError(err)
@ -290,7 +300,7 @@ func (suite *HandlerTestSuite) TestPayoutSwapClaimVVestingSingleDenom() {
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: cs(expectedRewards)},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward

View File

@ -44,7 +44,7 @@ func (suite *HandlerTestSuite) TestPayoutUSDXClaim() {
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...)) suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{ suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: expectedRewards},
}) })
// Check that claimed coins have been removed from a claim's reward // Check that claimed coins have been removed from a claim's reward
suite.USDXRewardEquals(userAddr, c(types.USDXMintingRewardDenom, 0)) suite.USDXRewardEquals(userAddr, c(types.USDXMintingRewardDenom, 0))
@ -88,7 +88,7 @@ func (suite *HandlerTestSuite) TestPayoutUSDXClaimVVesting() {
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards)) suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{ suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)}, {Length: (17+31+28+31+30+31+30+31+31+30+31+30+31)*secondsPerDay - 7, Amount: cs(expectedRewards)},
}) })
// Check that each claim reward coin's amount has been reset to 0 // Check that each claim reward coin's amount has been reset to 0

View File

@ -10,15 +10,20 @@ import (
// ClaimUSDXMintingReward pays out funds from a claim to a receiver account. // ClaimUSDXMintingReward pays out funds from a claim to a receiver account.
// Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times. // Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times.
func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName) error { func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName string) error {
claim, found := k.GetUSDXMintingClaim(ctx, owner) claim, found := k.GetUSDXMintingClaim(ctx, owner)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
} }
multiplier, found := k.GetMultiplier(ctx, multiplierName) name, err := types.ParseMultiplierName(multiplierName)
if err != nil {
return err
}
multiplier, found := k.GetMultiplierByDenom(ctx, types.USDXMintingRewardDenom, name)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", types.USDXMintingRewardDenom, name)
} }
claimEnd := k.GetClaimEnd(ctx) claimEnd := k.GetClaimEnd(ctx)
@ -27,7 +32,7 @@ func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, owner, receiver sdk.AccA
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd) return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
} }
claim, err := k.SynchronizeUSDXMintingClaim(ctx, claim) claim, err = k.SynchronizeUSDXMintingClaim(ctx, claim)
if err != nil { if err != nil {
return err return err
} }
@ -62,15 +67,15 @@ func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, owner, receiver sdk.AccA
// ClaimHardReward pays out funds from a claim to a receiver account. // ClaimHardReward pays out funds from a claim to a receiver account.
// Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times. // Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times.
func (k Keeper) ClaimHardReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error { func (k Keeper) ClaimHardReward(ctx sdk.Context, owner, receiver sdk.AccAddress, denom string, multiplierName string) error {
_, found := k.GetHardLiquidityProviderClaim(ctx, owner) name, err := types.ParseMultiplierName(multiplierName)
if !found { if err != nil {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return err
} }
multiplier, found := k.GetMultiplier(ctx, multiplierName) multiplier, found := k.GetMultiplierByDenom(ctx, denom, name)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", denom, name)
} }
claimEnd := k.GetClaimEnd(ctx) claimEnd := k.GetClaimEnd(ctx)
@ -86,8 +91,10 @@ func (k Keeper) ClaimHardReward(ctx sdk.Context, owner, receiver sdk.AccAddress,
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
} }
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim) amt := syncedClaim.Reward.AmountOf(denom)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
claimingCoins := sdk.NewCoins(sdk.NewCoin(denom, amt))
rewardCoins := sdk.NewCoins(sdk.NewCoin(denom, amt.ToDec().Mul(multiplier.Factor).RoundInt()))
if rewardCoins.IsZero() { if rewardCoins.IsZero() {
return types.ErrZeroClaim return types.ErrZeroClaim
} }
@ -118,15 +125,20 @@ func (k Keeper) ClaimHardReward(ctx sdk.Context, owner, receiver sdk.AccAddress,
// ClaimDelegatorReward pays out funds from a claim to a receiver account. // ClaimDelegatorReward pays out funds from a claim to a receiver account.
// Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times. // Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times.
func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error { func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, owner, receiver sdk.AccAddress, denom string, multiplierName string) error {
claim, found := k.GetDelegatorClaim(ctx, owner) claim, found := k.GetDelegatorClaim(ctx, owner)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
} }
multiplier, found := k.GetMultiplier(ctx, multiplierName) name, err := types.ParseMultiplierName(multiplierName)
if err != nil {
return err
}
multiplier, found := k.GetMultiplierByDenom(ctx, denom, name)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", denom, name)
} }
claimEnd := k.GetClaimEnd(ctx) claimEnd := k.GetClaimEnd(ctx)
@ -140,8 +152,10 @@ func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, owner, receiver sdk.AccAdd
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
} }
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim) amt := syncedClaim.Reward.AmountOf(denom)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
claimingCoins := sdk.NewCoins(sdk.NewCoin(denom, amt))
rewardCoins := sdk.NewCoins(sdk.NewCoin(denom, amt.ToDec().Mul(multiplier.Factor).RoundInt()))
if rewardCoins.IsZero() { if rewardCoins.IsZero() {
return types.ErrZeroClaim return types.ErrZeroClaim
} }
@ -173,15 +187,14 @@ func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, owner, receiver sdk.AccAdd
// ClaimSwapReward pays out funds from a claim to a receiver account. // ClaimSwapReward pays out funds from a claim to a receiver account.
// Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times. // Rewards are removed from a claim and paid out according to the multiplier, which reduces the reward amount in exchange for shorter vesting times.
func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error { func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress, denom string, multiplierName string) error {
_, found := k.GetSwapClaim(ctx, owner) name, err := types.ParseMultiplierName(multiplierName)
if !found { if err != nil {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return err
} }
multiplier, found := k.GetMultiplierByDenom(ctx, denom, name)
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", denom, name)
} }
claimEnd := k.GetClaimEnd(ctx) claimEnd := k.GetClaimEnd(ctx)
@ -195,8 +208,10 @@ func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress,
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
} }
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim) amt := syncedClaim.Reward.AmountOf(denom)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
claimingCoins := sdk.NewCoins(sdk.NewCoin(denom, amt))
rewardCoins := sdk.NewCoins(sdk.NewCoin(denom, amt.ToDec().Mul(multiplier.Factor).RoundInt()))
if rewardCoins.IsZero() { if rewardCoins.IsZero() {
return types.ErrZeroClaim return types.ErrZeroClaim
} }
@ -236,4 +251,3 @@ func (k Keeper) ValidateIsValidatorVestingAccount(ctx sdk.Context, address sdk.A
} }
return nil return nil
} }

View File

@ -26,8 +26,13 @@ func (suite *ClaimTests) ErrorIs(err, target error) bool {
func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() { func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() {
subspace := &fakeParamSubspace{ subspace := &fakeParamSubspace{
params: types.Params{ params: types.Params{
ClaimMultipliers: types.Multipliers{ ClaimMultipliers: types.MultipliersPerDenom{
types.NewMultiplier(types.Small, 1, d("0.2")), {
Denom: "hard",
Multipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, d("0.2")),
},
},
}, },
}, },
} }
@ -41,16 +46,11 @@ func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() {
suite.storeDelegatorClaim(claim) suite.storeDelegatorClaim(claim)
// multiplier not in params // multiplier not in params
err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, types.Large, nil) err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, "hard", "large")
suite.ErrorIs(err, types.ErrInvalidMultiplier) suite.ErrorIs(err, types.ErrInvalidMultiplier)
// invalid multiplier name // invalid multiplier name
err = suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, "", nil) err = suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, "hard", "")
suite.ErrorIs(err, types.ErrInvalidMultiplier)
// invalid multiplier name
const zeroWidthSpace = ""
err = suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, types.Small+zeroWidthSpace, nil)
suite.ErrorIs(err, types.ErrInvalidMultiplier) suite.ErrorIs(err, types.ErrInvalidMultiplier)
} }
@ -59,8 +59,13 @@ func (suite *ClaimTests) TestCannotClaimAfterEndTime() {
subspace := &fakeParamSubspace{ subspace := &fakeParamSubspace{
params: types.Params{ params: types.Params{
ClaimMultipliers: types.Multipliers{ ClaimMultipliers: types.MultipliersPerDenom{
types.NewMultiplier(types.Small, 1, d("0.2")), {
Denom: "hard",
Multipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, d("0.2")),
},
},
}, },
ClaimEnd: endTime, ClaimEnd: endTime,
}, },
@ -76,6 +81,6 @@ func (suite *ClaimTests) TestCannotClaimAfterEndTime() {
} }
suite.storeDelegatorClaim(claim) suite.storeDelegatorClaim(claim)
err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, types.Small, nil) err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, "hard", "small")
suite.ErrorIs(err, types.ErrClaimExpired) suite.ErrorIs(err, types.ErrClaimExpired)
} }

View File

@ -64,12 +64,14 @@ func (k Keeper) GetDelegatorRewardPeriods(ctx sdk.Context, denom string) (types.
return types.MultiRewardPeriod{}, false return types.MultiRewardPeriod{}, false
} }
// GetMultiplier returns the multiplier with the specified name if it's found in the params // GetMultiplierByDenom fetches a multiplier from the params matching the denom and name.
func (k Keeper) GetMultiplier(ctx sdk.Context, name types.MultiplierName) (types.Multiplier, bool) { func (k Keeper) GetMultiplierByDenom(ctx sdk.Context, denom string, name types.MultiplierName) (types.Multiplier, bool) {
params := k.GetParams(ctx) params := k.GetParams(ctx)
for _, m := range params.ClaimMultipliers {
if m.Name == name { for _, dm := range params.ClaimMultipliers {
return m, true if dm.Denom == denom {
m, found := dm.Multipliers.Get(name)
return m, found
} }
} }
return types.Multiplier{}, false return types.Multiplier{}, false

View File

@ -502,7 +502,7 @@ func (suite *PayoutTestSuite) TestGetPeriodLength() {
}, },
}, },
{ {
name: "exactly half of month", name: "exactly half of month, is pushed to start of month + lockup",
args: args{ args: args{
blockTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), blockTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
@ -525,6 +525,18 @@ func (suite *PayoutTestSuite) TestGetPeriodLength() {
contains: "", contains: "",
}, },
}, },
{
name: "just after start of month payout time, is pushed to mid month + lockup",
args: args{
blockTime: time.Date(2020, 12, 1, 14, 0, 1, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 1, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 1, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 1, 14, 0, 1, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {

View File

@ -222,7 +222,6 @@ func (suite *SupplyRewardsTestSuite) TestAccumulateHardSupplyRewards() {
}, },
}, },
}, },
// TODO test accumulate when there is a reward period with 0 rewardsPerSecond
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {

View File

@ -166,7 +166,7 @@ func (builder IncentiveGenesisBuilder) WithSimpleUSDXRewardPeriod(ctype string,
)) ))
} }
func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.Multipliers) IncentiveGenesisBuilder { func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.MultipliersPerDenom) IncentiveGenesisBuilder {
builder.Params.ClaimMultipliers = multipliers builder.Params.ClaimMultipliers = multipliers
return builder return builder
} }

View File

@ -18,7 +18,6 @@ var (
ErrZeroClaim = sdkerrors.Register(ModuleName, 9, "cannot claim - claim amount rounds to zero") ErrZeroClaim = sdkerrors.Register(ModuleName, 9, "cannot claim - claim amount rounds to zero")
ErrClaimExpired = sdkerrors.Register(ModuleName, 10, "claim has expired") ErrClaimExpired = sdkerrors.Register(ModuleName, 10, "claim has expired")
ErrInvalidClaimType = sdkerrors.Register(ModuleName, 11, "invalid claim type") ErrInvalidClaimType = sdkerrors.Register(ModuleName, 11, "invalid claim type")
ErrInvalidClaimOwner = sdkerrors.Register(ModuleName, 12, "invalid claim owner")
ErrDecreasingRewardFactor = sdkerrors.Register(ModuleName, 13, "found new reward factor less than an old reward factor") ErrDecreasingRewardFactor = sdkerrors.Register(ModuleName, 13, "found new reward factor less than an old reward factor")
ErrInvalidClaimDenoms = sdkerrors.Register(ModuleName, 14, "invalid claim denoms") ErrInvalidClaimDenoms = sdkerrors.Register(ModuleName, 14, "invalid claim denoms")
) )

View File

@ -45,8 +45,14 @@ func TestGenesisState_Validate(t *testing.T) {
DefaultMultiRewardPeriods, DefaultMultiRewardPeriods,
DefaultMultiRewardPeriods, DefaultMultiRewardPeriods,
DefaultMultiRewardPeriods, DefaultMultiRewardPeriods,
Multipliers{ MultipliersPerDenom{
NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33")), {
Denom: "ukava",
Multipliers: Multipliers{
NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33")),
NewMultiplier(Large, 12, sdk.MustNewDecFromStr("1.00")),
},
},
}, },
time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
), ),

View File

@ -110,17 +110,15 @@ func (msg MsgClaimUSDXMintingRewardVVesting) GetSigners() []sdk.AccAddress {
// MsgClaimHardReward message type used to claim Hard liquidity provider rewards // MsgClaimHardReward message type used to claim Hard liquidity provider rewards
type MsgClaimHardReward struct { type MsgClaimHardReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// NewMsgClaimHardReward returns a new MsgClaimHardReward. // NewMsgClaimHardReward returns a new MsgClaimHardReward.
func NewMsgClaimHardReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimHardReward { func NewMsgClaimHardReward(sender sdk.AccAddress, denomsToClaim ...Selection) MsgClaimHardReward {
return MsgClaimHardReward{ return MsgClaimHardReward{
Sender: sender, Sender: sender,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -137,17 +135,9 @@ func (msg MsgClaimHardReward) ValidateBasic() error {
if msg.Sender.Empty() { if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }
@ -164,19 +154,17 @@ func (msg MsgClaimHardReward) GetSigners() []sdk.AccAddress {
// MsgClaimHardRewardVVesting message type used to claim Hard liquidity provider rewards for validator vesting accounts // MsgClaimHardRewardVVesting message type used to claim Hard liquidity provider rewards for validator vesting accounts
type MsgClaimHardRewardVVesting struct { type MsgClaimHardRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// NewMsgClaimHardRewardVVesting returns a new MsgClaimHardRewardVVesting. // NewMsgClaimHardRewardVVesting returns a new MsgClaimHardRewardVVesting.
func NewMsgClaimHardRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimHardRewardVVesting { func NewMsgClaimHardRewardVVesting(sender, receiver sdk.AccAddress, denomsToClaim ...Selection) MsgClaimHardRewardVVesting {
return MsgClaimHardRewardVVesting{ return MsgClaimHardRewardVVesting{
Sender: sender, Sender: sender,
Receiver: receiver, Receiver: receiver,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -196,17 +184,9 @@ func (msg MsgClaimHardRewardVVesting) ValidateBasic() error {
if msg.Receiver.Empty() { if msg.Receiver.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }
@ -223,17 +203,15 @@ func (msg MsgClaimHardRewardVVesting) GetSigners() []sdk.AccAddress {
// MsgClaimDelegatorReward message type used to claim delegator rewards // MsgClaimDelegatorReward message type used to claim delegator rewards
type MsgClaimDelegatorReward struct { type MsgClaimDelegatorReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// NewMsgClaimDelegatorReward returns a new MsgClaimDelegatorReward. // NewMsgClaimDelegatorReward returns a new MsgClaimDelegatorReward.
func NewMsgClaimDelegatorReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimDelegatorReward { func NewMsgClaimDelegatorReward(sender sdk.AccAddress, denomsToClaim ...Selection) MsgClaimDelegatorReward {
return MsgClaimDelegatorReward{ return MsgClaimDelegatorReward{
Sender: sender, Sender: sender,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -250,17 +228,9 @@ func (msg MsgClaimDelegatorReward) ValidateBasic() error {
if msg.Sender.Empty() { if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }
@ -277,19 +247,17 @@ func (msg MsgClaimDelegatorReward) GetSigners() []sdk.AccAddress {
// MsgClaimDelegatorRewardVVesting message type used to claim delegator rewards for validator vesting accounts // MsgClaimDelegatorRewardVVesting message type used to claim delegator rewards for validator vesting accounts
type MsgClaimDelegatorRewardVVesting struct { type MsgClaimDelegatorRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// MsgClaimDelegatorRewardVVesting returns a new MsgClaimDelegatorRewardVVesting. // MsgClaimDelegatorRewardVVesting returns a new MsgClaimDelegatorRewardVVesting.
func NewMsgClaimDelegatorRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimDelegatorRewardVVesting { func NewMsgClaimDelegatorRewardVVesting(sender, receiver sdk.AccAddress, denomsToClaim ...Selection) MsgClaimDelegatorRewardVVesting {
return MsgClaimDelegatorRewardVVesting{ return MsgClaimDelegatorRewardVVesting{
Sender: sender, Sender: sender,
Receiver: receiver, Receiver: receiver,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -309,17 +277,9 @@ func (msg MsgClaimDelegatorRewardVVesting) ValidateBasic() error {
if msg.Receiver.Empty() { if msg.Receiver.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }
@ -338,15 +298,14 @@ func (msg MsgClaimDelegatorRewardVVesting) GetSigners() []sdk.AccAddress {
type MsgClaimSwapReward struct { type MsgClaimSwapReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// NewMsgClaimSwapReward returns a new MsgClaimSwapReward. // NewMsgClaimSwapReward returns a new MsgClaimSwapReward.
func NewMsgClaimSwapReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimSwapReward { func NewMsgClaimSwapReward(sender sdk.AccAddress, denomsToClaim ...Selection) MsgClaimSwapReward {
return MsgClaimSwapReward{ return MsgClaimSwapReward{
Sender: sender, Sender: sender,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -363,17 +322,9 @@ func (msg MsgClaimSwapReward) ValidateBasic() error {
if msg.Sender.Empty() { if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }
@ -390,19 +341,17 @@ func (msg MsgClaimSwapReward) GetSigners() []sdk.AccAddress {
// MsgClaimSwapRewardVVesting message type used to claim delegator rewards for validator vesting accounts // MsgClaimSwapRewardVVesting message type used to claim delegator rewards for validator vesting accounts
type MsgClaimSwapRewardVVesting struct { type MsgClaimSwapRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"` DenomsToClaim Selections `json:"denoms_to_claim" yaml:"denoms_to_claim"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }
// MsgClaimSwapRewardVVesting returns a new MsgClaimSwapRewardVVesting. // MsgClaimSwapRewardVVesting returns a new MsgClaimSwapRewardVVesting.
func NewMsgClaimSwapRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimSwapRewardVVesting { func NewMsgClaimSwapRewardVVesting(sender, receiver sdk.AccAddress, denomsToClaim ...Selection) MsgClaimSwapRewardVVesting {
return MsgClaimSwapRewardVVesting{ return MsgClaimSwapRewardVVesting{
Sender: sender, Sender: sender,
Receiver: receiver, Receiver: receiver,
MultiplierName: multiplierName, DenomsToClaim: denomsToClaim,
DenomsToClaim: denomsToClaim,
} }
} }
@ -422,17 +371,9 @@ func (msg MsgClaimSwapRewardVVesting) ValidateBasic() error {
if msg.Receiver.Empty() { if msg.Receiver.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty")
} }
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil { if err := msg.DenomsToClaim.Validate(); err != nil {
return err return err
} }
for i, d := range msg.DenomsToClaim {
if i >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
if err := sdk.ValidateDenom(d); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
}
return nil return nil
} }

View File

@ -21,10 +21,9 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
pass bool pass bool
} }
type msgArgs struct { type msgArgs struct {
sender sdk.AccAddress sender sdk.AccAddress
receiver sdk.AccAddress receiver sdk.AccAddress
multiplierName string denomsToClaim types.Selections
denomsToClaim []string
} }
tests := []struct { tests := []struct {
name string name string
@ -34,9 +33,14 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
{ {
name: "large multiplier is valid", name: "large multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "large", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
@ -45,9 +49,14 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
{ {
name: "medium multiplier is valid", name: "medium multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "medium", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
@ -56,54 +65,30 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
{ {
name: "small multiplier is valid", name: "small multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "small", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "small",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
}, },
}, },
{
name: "empty denoms to claim is valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "small",
denomsToClaim: []string{},
},
expect: expectedErr{
pass: true,
},
},
{
name: "invalid sender",
msgArgs: msgArgs{
sender: sdk.AccAddress{},
receiver: validAddress,
multiplierName: "medium",
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{
name: "invalid receiver",
msgArgs: msgArgs{
sender: validAddress,
receiver: sdk.AccAddress{},
multiplierName: "medium",
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{ {
name: "invalid multiplier", name: "invalid multiplier",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "huge", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "huge",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidMultiplier, wraps: types.ErrInvalidMultiplier,
@ -112,21 +97,85 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
{ {
name: "multiplier with capitalization is invalid", name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "Large", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "Large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidMultiplier, wraps: types.ErrInvalidMultiplier,
}, },
}, },
{
name: "empty denoms to claim is not valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
denomsToClaim: types.Selections{},
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "nil denoms to claim is not valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
denomsToClaim: nil,
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "invalid sender",
msgArgs: msgArgs{
sender: sdk.AccAddress{},
receiver: validAddress,
denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
},
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{
name: "invalid receiver",
msgArgs: msgArgs{
sender: validAddress,
receiver: sdk.AccAddress{},
denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
},
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{ {
name: "invalid claim denom", name: "invalid claim denom",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "small", denomsToClaim: types.Selections{
denomsToClaim: []string{"a denom string that is invalid because it is much too long"}, {
Denom: "a denom string that is invalid because it is much too long",
MultiplierName: "medium",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms, wraps: types.ErrInvalidClaimDenoms,
@ -135,10 +184,29 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
{ {
name: "too many claim denoms", name: "too many claim denoms",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
receiver: validAddress, receiver: validAddress,
multiplierName: "small", denomsToClaim: tooManySelections(),
denomsToClaim: tooManyClaimDenoms(), },
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "duplicated claim denoms",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
{
Denom: "hard",
MultiplierName: "large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms, wraps: types.ErrInvalidClaimDenoms,
@ -149,13 +217,13 @@ func TestMsgClaimVVesting_Validate(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
msgs := []sdk.Msg{ msgs := []sdk.Msg{
types.NewMsgClaimHardRewardVVesting( types.NewMsgClaimHardRewardVVesting(
tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim, tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.denomsToClaim...,
), ),
types.NewMsgClaimDelegatorRewardVVesting( types.NewMsgClaimDelegatorRewardVVesting(
tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim, tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.denomsToClaim...,
), ),
types.NewMsgClaimSwapRewardVVesting( types.NewMsgClaimSwapRewardVVesting(
tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim, tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.denomsToClaim...,
), ),
} }
for _, msg := range msgs { for _, msg := range msgs {
@ -180,20 +248,25 @@ func TestMsgClaim_Validate(t *testing.T) {
pass bool pass bool
} }
type msgArgs struct { type msgArgs struct {
sender sdk.AccAddress sender sdk.AccAddress
multiplierName string denomsToClaim types.Selections
denomsToClaim []string
} }
tests := []struct { tests := []struct {
name string name string
msgArgs msgArgs msgArgs msgArgs
expect expectedErr expect expectedErr
}{ }{
{ {
name: "large multiplier is valid", name: "large multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "large", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
@ -202,8 +275,13 @@ func TestMsgClaim_Validate(t *testing.T) {
{ {
name: "medium multiplier is valid", name: "medium multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "medium", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
@ -212,39 +290,28 @@ func TestMsgClaim_Validate(t *testing.T) {
{ {
name: "small multiplier is valid", name: "small multiplier is valid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "small", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "small",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
pass: true, pass: true,
}, },
}, },
{
name: "empty denoms to claim is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "small",
denomsToClaim: []string{},
},
expect: expectedErr{
pass: true,
},
},
{
name: "invalid sender",
msgArgs: msgArgs{
sender: sdk.AccAddress{},
multiplierName: "medium",
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{ {
name: "invalid multiplier", name: "invalid multiplier",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "huge", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "huge",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidMultiplier, wraps: types.ErrInvalidMultiplier,
@ -253,19 +320,63 @@ func TestMsgClaim_Validate(t *testing.T) {
{ {
name: "multiplier with capitalization is invalid", name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "Large", denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "Large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidMultiplier, wraps: types.ErrInvalidMultiplier,
}, },
}, },
{
name: "empty denoms to claim is not valid",
msgArgs: msgArgs{
sender: validAddress,
denomsToClaim: types.Selections{},
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "nil denoms to claim is not valid",
msgArgs: msgArgs{
sender: validAddress,
denomsToClaim: nil,
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "invalid sender",
msgArgs: msgArgs{
sender: sdk.AccAddress{},
denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
},
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{ {
name: "invalid claim denom", name: "invalid claim denom",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "small", denomsToClaim: types.Selections{
denomsToClaim: []string{"a denom string that is invalid because it is much too long"}, {
Denom: "a denom string that is invalid because it is much too long",
MultiplierName: "medium",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms, wraps: types.ErrInvalidClaimDenoms,
@ -274,9 +385,27 @@ func TestMsgClaim_Validate(t *testing.T) {
{ {
name: "too many claim denoms", name: "too many claim denoms",
msgArgs: msgArgs{ msgArgs: msgArgs{
sender: validAddress, sender: validAddress,
multiplierName: "small", denomsToClaim: tooManySelections(),
denomsToClaim: tooManyClaimDenoms(), },
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "duplicated claim denoms",
msgArgs: msgArgs{
sender: validAddress,
denomsToClaim: types.Selections{
{
Denom: "hard",
MultiplierName: "medium",
},
{
Denom: "hard",
MultiplierName: "large",
},
},
}, },
expect: expectedErr{ expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms, wraps: types.ErrInvalidClaimDenoms,
@ -286,15 +415,9 @@ func TestMsgClaim_Validate(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
msgs := []sdk.Msg{ msgs := []sdk.Msg{
types.NewMsgClaimHardReward( types.NewMsgClaimHardReward(tc.msgArgs.sender, tc.msgArgs.denomsToClaim...),
tc.msgArgs.sender, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim, types.NewMsgClaimDelegatorReward(tc.msgArgs.sender, tc.msgArgs.denomsToClaim...),
), types.NewMsgClaimSwapReward(tc.msgArgs.sender, tc.msgArgs.denomsToClaim...),
types.NewMsgClaimDelegatorReward(
tc.msgArgs.sender, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim,
),
types.NewMsgClaimSwapReward(
tc.msgArgs.sender, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim,
),
} }
for _, msg := range msgs { for _, msg := range msgs {
t.Run(msg.Type()+" "+tc.name, func(t *testing.T) { t.Run(msg.Type()+" "+tc.name, func(t *testing.T) {
@ -520,3 +643,14 @@ func tooManyClaimDenoms() []string {
} }
return claimDenoms return claimDenoms
} }
func tooManySelections() types.Selections {
selections := make(types.Selections, types.MaxDenomsToClaim+1)
for i := range selections {
selections[i] = types.Selection{
Denom: fmt.Sprintf("denom%d", i),
MultiplierName: "large",
}
}
return selections
}

View File

@ -0,0 +1,201 @@
package types
import (
"fmt"
"sort"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Available reward multipliers names
const (
Small MultiplierName = "small"
Medium MultiplierName = "medium"
Large MultiplierName = "large"
)
// MultiplierName is the user facing ID for a multiplier. There is a restricted set of possible values.
type MultiplierName string
// IsValid checks if the input is one of the expected strings
func (mn MultiplierName) IsValid() error {
switch mn {
case Small, Medium, Large:
return nil
}
return sdkerrors.Wrapf(ErrInvalidMultiplier, "invalid multiplier name: %s", mn)
}
// ParseMultiplierName converts a string into a valid MultiplierName value.
func ParseMultiplierName(unparsedName string) (MultiplierName, error) {
name := MultiplierName(unparsedName)
if err := name.IsValid(); err != nil {
return "", err
}
return name, nil
}
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
type Multiplier struct {
Name MultiplierName `json:"name" yaml:"name"`
MonthsLockup int64 `json:"months_lockup" yaml:"months_lockup"`
Factor sdk.Dec `json:"factor" yaml:"factor"`
}
// NewMultiplier returns a new Multiplier
func NewMultiplier(name MultiplierName, lockup int64, factor sdk.Dec) Multiplier {
return Multiplier{
Name: name,
MonthsLockup: lockup,
Factor: factor,
}
}
// Validate multiplier param
func (m Multiplier) Validate() error {
if err := m.Name.IsValid(); err != nil {
return err
}
if m.MonthsLockup < 0 {
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
}
if m.Factor.IsNegative() {
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
}
return nil
}
// String implements fmt.Stringer
func (m Multiplier) String() string {
return fmt.Sprintf(`Claim Multiplier:
Name: %s
Months Lockup %d
Factor %s
`, m.Name, m.MonthsLockup, m.Factor)
}
// Multipliers is a slice of Multiplier
type Multipliers []Multiplier
// Validate validates each multiplier
func (ms Multipliers) Validate() error {
for _, m := range ms {
if err := m.Validate(); err != nil {
return err
}
}
return nil
}
// Get returns a multiplier with a matching name
func (ms Multipliers) Get(name MultiplierName) (Multiplier, bool) {
for _, m := range ms {
if m.Name == name {
return m, true
}
}
return Multiplier{}, false
}
// String implements fmt.Stringer
func (ms Multipliers) String() string {
out := "Claim Multipliers\n"
for _, s := range ms {
out += fmt.Sprintf("%s\n", s)
}
return out
}
// MultipliersPerDenom is a map of denoms to a set of multipliers
type MultipliersPerDenom []struct {
Denom string `json:"denom" yaml:"denom"`
Multipliers Multipliers `json:"multipliers" yaml:"multipliers"`
}
// Validate checks each denom and multipliers for invalid values.
func (mpd MultipliersPerDenom) Validate() error {
foundDenoms := map[string]bool{}
for _, item := range mpd {
if err := sdk.ValidateDenom(item.Denom); err != nil {
return err
}
if err := item.Multipliers.Validate(); err != nil {
return err
}
if foundDenoms[item.Denom] {
return fmt.Errorf("")
}
foundDenoms[item.Denom] = true
}
return nil
}
// Selection a pair of denom and multiplier name. It holds the choice of multiplier a user makes when they claim a denom.
type Selection struct {
Denom string `json:"denom" yaml:"denom"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
// NewSelection returns a new Selection
func NewSelection(denom, multiplierName string) Selection {
return Selection{
Denom: denom,
MultiplierName: multiplierName,
}
}
// Validate performs basic validation checks
func (s Selection) Validate() error {
if err := sdk.ValidateDenom(s.Denom); err != nil {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, err.Error())
}
if _, err := ParseMultiplierName(s.MultiplierName); err != nil {
return err
}
return nil
}
// Selections are a list of denom - multiplier pairs that specify what rewards to claim and with what lockups.
type Selections []Selection
// NewSelectionsFromMap creates a new set of selections from a string to string map.
// It sorts the output before returning.
func NewSelectionsFromMap(selectionMap map[string]string) Selections {
var selections Selections
for k, v := range selectionMap {
selections = append(selections, NewSelection(k, v))
}
// deterministically sort the slice to protect against the random range order causing consensus failures
sort.Slice(selections, func(i, j int) bool {
if selections[i].Denom != selections[j].Denom {
return selections[i].Denom < selections[j].Denom
}
return selections[i].MultiplierName < selections[j].MultiplierName
})
return selections
}
// Valdate performs basic validaton checks
func (ss Selections) Validate() error {
if len(ss) == 0 {
return sdkerrors.Wrap(ErrInvalidClaimDenoms, "cannot claim 0 denoms")
}
if len(ss) >= MaxDenomsToClaim {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim more than %d denoms", MaxDenomsToClaim)
}
foundDenoms := map[string]bool{}
for _, s := range ss {
if err := s.Validate(); err != nil {
return err
}
if foundDenoms[s.Denom] {
return sdkerrors.Wrapf(ErrInvalidClaimDenoms, "cannot claim denom '%s' more than once", s.Denom)
}
foundDenoms[s.Denom] = true
}
return nil
}

View File

@ -7,20 +7,12 @@ import (
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/params"
tmtime "github.com/tendermint/tendermint/types/time" tmtime "github.com/tendermint/tendermint/types/time"
kavadistTypes "github.com/kava-labs/kava/x/kavadist/types" kavadistTypes "github.com/kava-labs/kava/x/kavadist/types"
) )
// Valid reward multipliers
const (
Small MultiplierName = "small"
Medium MultiplierName = "medium"
Large MultiplierName = "large"
)
// Parameter keys and default values // Parameter keys and default values
var ( var (
KeyUSDXMintingRewardPeriods = []byte("USDXMintingRewardPeriods") KeyUSDXMintingRewardPeriods = []byte("USDXMintingRewardPeriods")
@ -34,7 +26,7 @@ var (
DefaultActive = false DefaultActive = false
DefaultRewardPeriods = RewardPeriods{} DefaultRewardPeriods = RewardPeriods{}
DefaultMultiRewardPeriods = MultiRewardPeriods{} DefaultMultiRewardPeriods = MultiRewardPeriods{}
DefaultMultipliers = Multipliers{} DefaultMultipliers = MultipliersPerDenom{}
DefaultClaimEnd = tmtime.Canonical(time.Unix(1, 0)) DefaultClaimEnd = tmtime.Canonical(time.Unix(1, 0))
BondDenom = "ukava" BondDenom = "ukava"
@ -45,18 +37,18 @@ var (
// Params governance parameters for the incentive module // Params governance parameters for the incentive module
type Params struct { type Params struct {
USDXMintingRewardPeriods RewardPeriods `json:"usdx_minting_reward_periods" yaml:"usdx_minting_reward_periods"` USDXMintingRewardPeriods RewardPeriods `json:"usdx_minting_reward_periods" yaml:"usdx_minting_reward_periods"`
HardSupplyRewardPeriods MultiRewardPeriods `json:"hard_supply_reward_periods" yaml:"hard_supply_reward_periods"` HardSupplyRewardPeriods MultiRewardPeriods `json:"hard_supply_reward_periods" yaml:"hard_supply_reward_periods"`
HardBorrowRewardPeriods MultiRewardPeriods `json:"hard_borrow_reward_periods" yaml:"hard_borrow_reward_periods"` HardBorrowRewardPeriods MultiRewardPeriods `json:"hard_borrow_reward_periods" yaml:"hard_borrow_reward_periods"`
DelegatorRewardPeriods MultiRewardPeriods `json:"delegator_reward_periods" yaml:"delegator_reward_periods"` DelegatorRewardPeriods MultiRewardPeriods `json:"delegator_reward_periods" yaml:"delegator_reward_periods"`
SwapRewardPeriods MultiRewardPeriods `json:"swap_reward_periods" yaml:"swap_reward_periods"` SwapRewardPeriods MultiRewardPeriods `json:"swap_reward_periods" yaml:"swap_reward_periods"`
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` ClaimMultipliers MultipliersPerDenom `json:"claim_multipliers" yaml:"claim_multipliers"`
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
} }
// NewParams returns a new params object // NewParams returns a new params object
func NewParams(usdxMinting RewardPeriods, hardSupply, hardBorrow, delegator, swap MultiRewardPeriods, func NewParams(usdxMinting RewardPeriods, hardSupply, hardBorrow, delegator, swap MultiRewardPeriods,
multipliers Multipliers, claimEnd time.Time) Params { multipliers MultipliersPerDenom, claimEnd time.Time) Params {
return Params{ return Params{
USDXMintingRewardPeriods: usdxMinting, USDXMintingRewardPeriods: usdxMinting,
HardSupplyRewardPeriods: hardSupply, HardSupplyRewardPeriods: hardSupply,
@ -89,7 +81,7 @@ func (p Params) String() string {
Hard Borrow Reward Periods: %s Hard Borrow Reward Periods: %s
Delegator Reward Periods: %s Delegator Reward Periods: %s
Swap Reward Periods: %s Swap Reward Periods: %s
Claim Multipliers :%s Claim Multipliers: %s
Claim End Time: %s Claim End Time: %s
`, p.USDXMintingRewardPeriods, p.HardSupplyRewardPeriods, p.HardBorrowRewardPeriods, `, p.USDXMintingRewardPeriods, p.HardSupplyRewardPeriods, p.HardBorrowRewardPeriods,
p.DelegatorRewardPeriods, p.SwapRewardPeriods, p.ClaimMultipliers, p.ClaimEnd) p.DelegatorRewardPeriods, p.SwapRewardPeriods, p.ClaimMultipliers, p.ClaimEnd)
@ -108,15 +100,15 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
params.NewParamSetPair(KeyHardBorrowRewardPeriods, &p.HardBorrowRewardPeriods, validateMultiRewardPeriodsParam), params.NewParamSetPair(KeyHardBorrowRewardPeriods, &p.HardBorrowRewardPeriods, validateMultiRewardPeriodsParam),
params.NewParamSetPair(KeyDelegatorRewardPeriods, &p.DelegatorRewardPeriods, validateMultiRewardPeriodsParam), params.NewParamSetPair(KeyDelegatorRewardPeriods, &p.DelegatorRewardPeriods, validateMultiRewardPeriodsParam),
params.NewParamSetPair(KeySwapRewardPeriods, &p.SwapRewardPeriods, validateMultiRewardPeriodsParam), params.NewParamSetPair(KeySwapRewardPeriods, &p.SwapRewardPeriods, validateMultiRewardPeriodsParam),
params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersPerDenomParam),
params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam), params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam),
params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam),
} }
} }
// Validate checks that the parameters have valid values. // Validate checks that the parameters have valid values.
func (p Params) Validate() error { func (p Params) Validate() error {
if err := validateMultipliersParam(p.ClaimMultipliers); err != nil { if err := validateMultipliersPerDenomParam(p.ClaimMultipliers); err != nil {
return err return err
} }
@ -169,6 +161,14 @@ func validateMultipliersParam(i interface{}) error {
return multipliers.Validate() return multipliers.Validate()
} }
func validateMultipliersPerDenomParam(i interface{}) error {
multipliers, ok := i.(MultipliersPerDenom)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
return multipliers.Validate()
}
func validateClaimEndParam(i interface{}) error { func validateClaimEndParam(i interface{}) error {
endTime, ok := i.(time.Time) endTime, ok := i.(time.Time)
if !ok { if !ok {
@ -360,77 +360,3 @@ func (mrps MultiRewardPeriods) Validate() error {
return nil return nil
} }
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
type Multiplier struct {
Name MultiplierName `json:"name" yaml:"name"`
MonthsLockup int64 `json:"months_lockup" yaml:"months_lockup"`
Factor sdk.Dec `json:"factor" yaml:"factor"`
}
// NewMultiplier returns a new Multiplier
func NewMultiplier(name MultiplierName, lockup int64, factor sdk.Dec) Multiplier {
return Multiplier{
Name: name,
MonthsLockup: lockup,
Factor: factor,
}
}
// Validate multiplier param
func (m Multiplier) Validate() error {
if err := m.Name.IsValid(); err != nil {
return err
}
if m.MonthsLockup < 0 {
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
}
if m.Factor.IsNegative() {
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
}
return nil
}
// String implements fmt.Stringer
func (m Multiplier) String() string {
return fmt.Sprintf(`Claim Multiplier:
Name: %s
Months Lockup %d
Factor %s
`, m.Name, m.MonthsLockup, m.Factor)
}
// Multipliers slice of Multiplier
type Multipliers []Multiplier
// Validate validates each multiplier
func (ms Multipliers) Validate() error {
for _, m := range ms {
if err := m.Validate(); err != nil {
return err
}
}
return nil
}
// String implements fmt.Stringer
func (ms Multipliers) String() string {
out := "Claim Multipliers\n"
for _, s := range ms {
out += fmt.Sprintf("%s\n", s)
}
return out
}
// MultiplierName name for valid multiplier
type MultiplierName string
// IsValid checks if the input is one of the expected strings
func (mn MultiplierName) IsValid() error {
switch mn {
case Small, Medium, Large:
return nil
}
return sdkerrors.Wrapf(ErrInvalidMultiplier, "invalid multiplier name: %s", mn)
}

View File

@ -77,19 +77,27 @@ func (suite *ParamTestSuite) TestParamValidation() {
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)), sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)),
)}, )},
ClaimMultipliers: types.Multipliers{
types.NewMultiplier(
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
),
types.NewMultiplier(
types.Large, 1, sdk.MustNewDecFromStr("1.0"),
),
},
HardSupplyRewardPeriods: types.DefaultMultiRewardPeriods, HardSupplyRewardPeriods: types.DefaultMultiRewardPeriods,
HardBorrowRewardPeriods: types.DefaultMultiRewardPeriods, HardBorrowRewardPeriods: types.DefaultMultiRewardPeriods,
DelegatorRewardPeriods: types.DefaultMultiRewardPeriods, DelegatorRewardPeriods: types.DefaultMultiRewardPeriods,
SwapRewardPeriods: types.DefaultMultiRewardPeriods, SwapRewardPeriods: types.DefaultMultiRewardPeriods,
ClaimEnd: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), ClaimMultipliers: types.MultipliersPerDenom{
{
Denom: "hard",
Multipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.25")),
types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0")),
},
},
{
Denom: "ukava",
Multipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.2")),
types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0")),
},
},
},
ClaimEnd: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
}, },
errArgs{ errArgs{
expectPass: true, expectPass: true,

View File

@ -18,29 +18,3 @@ func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
} }
return length return length
} }
// MultiplyCoins multiplies each value in a set of coins by a single decimal value, rounding the result.
func MultiplyCoins(coins sdk.Coins, multiple sdk.Dec) sdk.Coins {
var result sdk.Coins
for _, coin := range coins {
result = result.Add(
sdk.NewCoin(coin.Denom, coin.Amount.ToDec().Mul(multiple).RoundInt()),
)
}
return result
}
// FilterCoins returns a subset of the coins by denom. Specifying no denoms will return the original coins.
func FilterCoins(coins sdk.Coins, denoms []string) sdk.Coins {
if len(denoms) == 0 {
// with no filter, return all the coins
return coins
}
// otherwise select denoms in filter
var filteredCoins sdk.Coins
for _, denom := range denoms {
filteredCoins = filteredCoins.Add(sdk.NewCoin(denom, coins.AmountOf(denom)))
}
return filteredCoins
}

View File

@ -5,7 +5,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
@ -43,128 +42,3 @@ func TestGetTotalVestingPeriodLength(t *testing.T) {
}) })
} }
} }
func TestMultiplyCoins(t *testing.T) {
testCases := []struct {
name string
coins sdk.Coins
multiple sdk.Dec
expected sdk.Coins
}{
{
name: "decimals are rounded to nearest even, up",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
),
multiple: sdk.MustNewDecFromStr("3.1415"),
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 3142),
),
},
{
name: "decimals are rounded to nearest even, down",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e7),
),
multiple: sdk.MustNewDecFromStr("3.14159265"),
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 31415926),
),
},
{
name: "multiple coin amounts are multiplied",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1),
sdk.NewInt64Coin("ukava", 1e18),
),
multiple: sdk.MustNewDecFromStr("2.000000000000000002"),
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 2),
sdk.NewInt64Coin("ukava", 2_000_000_000_000_000_002),
),
},
{
name: "empty coins return nil",
coins: sdk.Coins{},
multiple: sdk.MustNewDecFromStr("2.5"),
expected: nil,
},
{
name: "nil coins return nil",
coins: nil,
multiple: sdk.MustNewDecFromStr("2.5"),
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t,
tc.expected,
types.MultiplyCoins(tc.coins, tc.multiple),
)
})
}
}
func TestFilterCoins(t *testing.T) {
testCases := []struct {
name string
coins sdk.Coins
denoms []string
expected sdk.Coins
}{
{
name: "non-empty filter selects subset of coins",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("ukava", 2e3),
sdk.NewInt64Coin("btc", 3e3),
),
denoms: []string{"hard", "btc"},
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("btc", 3e3),
),
},
{
name: "when coins are nil a non-empty filter returns nil coins",
coins: nil,
denoms: []string{"hard", "btc"},
expected: nil,
},
{
name: "nil filter returns original coins",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("ukava", 2e3),
),
denoms: nil,
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("ukava", 2e3),
),
},
{
name: "empty filter returns original coins",
coins: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("ukava", 2e3),
),
denoms: []string{},
expected: sdk.NewCoins(
sdk.NewInt64Coin("hard", 1e3),
sdk.NewInt64Coin("ukava", 2e3),
),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t,
tc.expected,
types.FilterCoins(tc.coins, tc.denoms),
)
})
}
}