Refactor incentive payout (#953), Users can claim swap rewards (#955)

* split up payout.go file

* extract genesis builders to new testutil package

* move claim integration tests out of keeper

* convert claim integration tests to handler tests

* combine claim usdx minting keeper methods

* combine hard claim keeper methods

* combine delegator claim keeper methods

* add multiply coins helper method

* rename file to better match contents

* add basic claiming unit tests

* add claiming subset of delegator reward denoms

* refactor msg tests

* add msg ValidateBasic tests

* connect swap hooks into keeper methods

* tidy up delegator handler tests

* add swap claiming msgs and keeper method

* add swap claiming to client

* add subset claiming to other msg types

* split up handler test file

* connect up subset claiming for swap

* make multiplier name validation more strict

* fix: struct tag typo in swap incentives

* connect up subset claiming for hard

* connect up subset claiming for delegator

* fix: register cli tx routes for swp claiming

* fix claim amount in claim event

* fix token name in cli help docs

* remove unused field in msg tests

* tidy up swap and delegator handler tests

* refactor hard handler tests

* refactor usdx handler tests

* remove unused constant

Co-authored-by: karzak <kjydavis3@gmail.com>
This commit is contained in:
Ruaridh 2021-07-15 15:05:54 +01:00 committed by GitHub
parent 20437a91fb
commit 38a98ac4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2817 additions and 1855 deletions

View File

@ -26,6 +26,7 @@ const (
EventTypeRewardPeriod = types.EventTypeRewardPeriod EventTypeRewardPeriod = types.EventTypeRewardPeriod
HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType
Large = types.Large Large = types.Large
MaxDenomsToClaim = types.MaxDenomsToClaim
Medium = types.Medium Medium = types.Medium
ModuleName = types.ModuleName ModuleName = types.ModuleName
QuerierRoute = types.QuerierRoute QuerierRoute = types.QuerierRoute
@ -53,7 +54,9 @@ 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
NewAccumulator = types.NewAccumulator NewAccumulator = types.NewAccumulator
NewDelegatorClaim = types.NewDelegatorClaim NewDelegatorClaim = types.NewDelegatorClaim
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
@ -63,6 +66,8 @@ var (
NewMsgClaimDelegatorRewardVVesting = types.NewMsgClaimDelegatorRewardVVesting NewMsgClaimDelegatorRewardVVesting = types.NewMsgClaimDelegatorRewardVVesting
NewMsgClaimHardReward = types.NewMsgClaimHardReward NewMsgClaimHardReward = types.NewMsgClaimHardReward
NewMsgClaimHardRewardVVesting = types.NewMsgClaimHardRewardVVesting NewMsgClaimHardRewardVVesting = types.NewMsgClaimHardRewardVVesting
NewMsgClaimSwapReward = types.NewMsgClaimSwapReward
NewMsgClaimSwapRewardVVesting = types.NewMsgClaimSwapRewardVVesting
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting
NewMultiRewardIndex = types.NewMultiRewardIndex NewMultiRewardIndex = types.NewMultiRewardIndex
@ -98,6 +103,7 @@ var (
ErrDecreasingRewardFactor = types.ErrDecreasingRewardFactor ErrDecreasingRewardFactor = types.ErrDecreasingRewardFactor
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
ErrInvalidAccountType = types.ErrInvalidAccountType ErrInvalidAccountType = types.ErrInvalidAccountType
ErrInvalidClaimDenoms = types.ErrInvalidClaimDenoms
ErrInvalidClaimOwner = types.ErrInvalidClaimOwner ErrInvalidClaimOwner = types.ErrInvalidClaimOwner
ErrInvalidClaimType = types.ErrInvalidClaimType ErrInvalidClaimType = types.ErrInvalidClaimType
ErrInvalidMultiplier = types.ErrInvalidMultiplier ErrInvalidMultiplier = types.ErrInvalidMultiplier
@ -107,7 +113,6 @@ var (
GovDenom = types.GovDenom GovDenom = types.GovDenom
HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix
HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix
HardLiquidityRewardDenom = types.HardLiquidityRewardDenom
HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix
IncentiveMacc = types.IncentiveMacc IncentiveMacc = types.IncentiveMacc
KeyClaimEnd = types.KeyClaimEnd KeyClaimEnd = types.KeyClaimEnd
@ -155,6 +160,8 @@ type (
MsgClaimDelegatorRewardVVesting = types.MsgClaimDelegatorRewardVVesting MsgClaimDelegatorRewardVVesting = types.MsgClaimDelegatorRewardVVesting
MsgClaimHardReward = types.MsgClaimHardReward MsgClaimHardReward = types.MsgClaimHardReward
MsgClaimHardRewardVVesting = types.MsgClaimHardRewardVVesting MsgClaimHardRewardVVesting = types.MsgClaimHardRewardVVesting
MsgClaimSwapReward = types.MsgClaimSwapReward
MsgClaimSwapRewardVVesting = types.MsgClaimSwapRewardVVesting
MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward
MsgClaimUSDXMintingRewardVVesting = types.MsgClaimUSDXMintingRewardVVesting MsgClaimUSDXMintingRewardVVesting = types.MsgClaimUSDXMintingRewardVVesting
MultiRewardIndex = types.MultiRewardIndex MultiRewardIndex = types.MultiRewardIndex

View File

@ -32,23 +32,21 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
getCmdClaimHardVVesting(cdc), getCmdClaimHardVVesting(cdc),
getCmdClaimDelegator(cdc), getCmdClaimDelegator(cdc),
getCmdClaimDelegatorVVesting(cdc), getCmdClaimDelegatorVVesting(cdc),
getCmdClaimSwap(cdc),
getCmdClaimSwapVVesting(cdc),
)...) )...)
return incentiveTxCmd return incentiveTxCmd
} }
func getCmdClaimCdp(cdc *codec.Codec) *cobra.Command { func getCmdClaimCdp(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim-cdp [multiplier]",
Short: "claim CDP rewards using a given multiplier",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding CDP rewards using a given multiplier
Example: cmd := &cobra.Command{
$ %s tx %s claim-cdp large Use: "claim-cdp [multiplier]",
`, version.ClientName, types.ModuleName), Short: "claim USDX minting rewards using a given multiplier",
), Long: `Claim sender's outstanding USDX minting rewards using a given multiplier.`,
Args: cobra.ExactArgs(1), Example: fmt.Sprintf(` $ %s tx %s claim-cdp large`, version.ClientName, types.ModuleName),
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)
@ -58,27 +56,25 @@ func getCmdClaimCdp(cdc *codec.Codec) *cobra.Command {
multiplier := args[0] multiplier := args[0]
msg := types.NewMsgClaimUSDXMintingReward(sender, multiplier) msg := types.NewMsgClaimUSDXMintingReward(sender, multiplier)
err := msg.ValidateBasic() if err := msg.ValidateBasic(); err != nil {
if err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
return cmd
} }
func getCmdClaimCdpVVesting(cdc *codec.Codec) *cobra.Command { func getCmdClaimCdpVVesting(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim-cdp-vesting [multiplier] [receiver]",
Short: "claim CDP rewards using a given multiplier on behalf of a validator vesting account",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding CDP rewards on behalf of a validator vesting using a given multiplier
Example: cmd := &cobra.Command{
$ %s tx %s claim-cdp-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw Use: "claim-cdp-vesting [multiplier] [receiver]",
`, version.ClientName, types.ModuleName), Short: "claim USDX minting rewards using a given multiplier on behalf of a validator vesting account",
), Long: `Claim sender's outstanding USDX minting rewards on behalf of a validator vesting using a given multiplier.
Args: cobra.ExactArgs(2), A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.`,
Example: fmt.Sprintf(` $ %s tx %s claim-cdp-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw`, version.ClientName, types.ModuleName),
Args: cobra.ExactArgs(2),
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)
@ -93,62 +89,25 @@ func getCmdClaimCdpVVesting(cdc *codec.Codec) *cobra.Command {
} }
msg := types.NewMsgClaimUSDXMintingRewardVVesting(sender, receiver, multiplier) msg := types.NewMsgClaimUSDXMintingRewardVVesting(sender, receiver, multiplier)
err = msg.ValidateBasic() if err := msg.ValidateBasic(); err != nil {
if err != nil {
return err return err
} }
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
}, },
} }
}
func getCmdClaimHardVVesting(cdc *codec.Codec) *cobra.Command { return cmd
return &cobra.Command{
Use: "claim-hard-vesting [multiplier] [receiver]",
Short: "claim Hard module rewards on behalf of a validator vesting account using a given multiplier",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding Hard rewards on behalf of a validator vesting account for deposit/borrow/delegate using given multiplier
Example:
$ %s tx %s claim-hard-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
`, version.ClientName, types.ModuleName),
),
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 {
return err
}
msg := types.NewMsgClaimHardRewardVVesting(sender, receiver, multiplier)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
} }
func getCmdClaimHard(cdc *codec.Codec) *cobra.Command { func getCmdClaimHard(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ var denomsToClaim []string
Use: "claim-hard [multiplier]",
Short: "claim sender's Hard module rewards using a given multiplier",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding Hard rewards for deposit/borrow/delegate using given multiplier
Example: cmd := &cobra.Command{
$ %s tx %s claim-hard large Use: "claim-hard [multiplier]",
`, version.ClientName, types.ModuleName), Short: "claim sender's Hard module rewards using a given multiplier",
), Long: `Claim sender's outstanding Hard rewards for deposit/borrow using given multiplier`,
Args: cobra.ExactArgs(1), Example: fmt.Sprintf(` $ %s tx %s claim-hard large`, version.ClientName, types.ModuleName),
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)
@ -157,57 +116,31 @@ func getCmdClaimHard(cdc *codec.Codec) *cobra.Command {
sender := cliCtx.GetFromAddress() sender := cliCtx.GetFromAddress()
multiplier := args[0] multiplier := args[0]
msg := types.NewMsgClaimHardReward(sender, multiplier) msg := types.NewMsgClaimHardReward(sender, multiplier, denomsToClaim)
err := msg.ValidateBasic() if err := msg.ValidateBasic(); err != nil {
if 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")
return cmd
} }
func getCmdClaimDelegator(cdc *codec.Codec) *cobra.Command { func getCmdClaimHardVVesting(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ var denomsToClaim []string
Use: "claim-delegator [multiplier]",
Short: "claim sender's delegator rewards using a given multiplier",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding delegator rewards using given multiplier
Example: cmd := &cobra.Command{
$ %s tx %s claim-delegator large Use: "claim-hard-vesting [multiplier] [receiver]",
`, version.ClientName, types.ModuleName), Short: "claim Hard module rewards on behalf of a validator vesting account using a given multiplier",
), Long: `Claim sender's outstanding hard supply/borrow rewards on behalf of a validator vesting account using given multiplier
Args: cobra.ExactArgs(1), A receiver address for the rewards is needed as validator vesting accounts cannot receive locked tokens.
RunE: func(cmd *cobra.Command, args []string) error { Optionally claim only certain denoms from the rewards. Specifying none will claim all of them.`,
inBuf := bufio.NewReader(cmd.InOrStdin()) Example: strings.Join([]string{
cliCtx := context.NewCLIContext().WithCodec(cdc) fmt.Sprintf(" $ %s tx %s claim-hard-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw", version.ClientName, types.ModuleName),
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) fmt.Sprintf(" $ %s tx %s claim-hard-vesting small kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw --claim-only swp,hard", version.ClientName, types.ModuleName),
}, "\n"),
sender := cliCtx.GetFromAddress()
multiplier := args[0]
msg := types.NewMsgClaimDelegatorReward(sender, multiplier)
err := msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func getCmdClaimDelegatorVVesting(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim-delegator-vesting [multiplier] [receiver]",
Short: "claim delegator rewards on behalf of a validator vesting account using a given multiplier",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim sender's outstanding delegator rewards on behalf of a validator vesting account using given multiplier
Example:
$ %s tx %s claim-delegator-vesting large kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
`, version.ClientName, types.ModuleName),
),
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
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())
@ -222,12 +155,158 @@ func getCmdClaimDelegatorVVesting(cdc *codec.Codec) *cobra.Command {
return err return err
} }
msg := types.NewMsgClaimDelegatorRewardVVesting(sender, receiver, multiplier) msg := types.NewMsgClaimHardRewardVVesting(sender, receiver, multiplier, denomsToClaim)
err = msg.ValidateBasic() if err := msg.ValidateBasic(); err != nil {
if 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")
return cmd
}
func getCmdClaimDelegator(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string
cmd := &cobra.Command{
Use: "claim-delegator [multiplier]",
Short: "claim sender's delegator rewards using a given multiplier",
Long: `Claim sender's outstanding delegator rewards using given multiplier.
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 large", version.ClientName, types.ModuleName),
fmt.Sprintf(" $ %s tx %s claim-delegator large --claim-only swp,hard", version.ClientName, types.ModuleName),
}, "\n"),
Args: cobra.ExactArgs(1),
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]
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 {
return err
}
msg := types.NewMsgClaimDelegatorRewardVVesting(sender, receiver, 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 getCmdClaimSwap(cdc *codec.Codec) *cobra.Command {
var denomsToClaim []string
cmd := &cobra.Command{
Use: "claim-swap [multiplier]",
Short: "claim sender's swap rewards using a given multiplier",
Long: `Claim sender's outstanding swap rewards using given multiplier.
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 large", version.ClientName, types.ModuleName),
fmt.Sprintf(" $ %s tx %s claim-swap large --claim-only swp,hard", version.ClientName, types.ModuleName),
}, "\n"),
Args: cobra.ExactArgs(1),
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]
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 {
return err
}
msg := types.NewMsgClaimSwapRewardVVesting(sender, receiver, 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
} }

View File

@ -25,6 +25,7 @@ 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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
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.
@ -33,4 +34,5 @@ type PostClaimVVestingReq 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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
} }

View File

@ -16,15 +16,33 @@ import (
) )
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/incentive/claim-cdp", postClaimCdpHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/incentive/claim-cdp", postClaimHandlerFn(cliCtx, usdxMintingGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-cdp-vesting", postClaimCdpVVestingHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/incentive/claim-cdp-vesting", postClaimVVestingHandlerFn(cliCtx, usdxMintingVVGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-hard", postClaimHardHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-hard-vesting", postClaimHardVVestingHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/incentive/claim-hard", postClaimHandlerFn(cliCtx, hardGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-delegator", postClaimDelegatorHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/incentive/claim-hard-vesting", postClaimVVestingHandlerFn(cliCtx, hardVVGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-delegator-vesting", postClaimDelegatorVVestingHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-delegator", postClaimHandlerFn(cliCtx, delegatorGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-delegator-vesting", postClaimVVestingHandlerFn(cliCtx, delegatorVVGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-swap", postClaimHandlerFn(cliCtx, swapGenerator)).Methods("POST")
r.HandleFunc("/incentive/claim-swap-vesting", postClaimVVestingHandlerFn(cliCtx, swapVVGenerator)).Methods("POST")
} }
func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func usdxMintingGenerator(req PostClaimReq) sdk.Msg {
return types.NewMsgClaimUSDXMintingReward(req.Sender, req.MultiplierName)
}
func hardGenerator(req PostClaimReq) sdk.Msg {
return types.NewMsgClaimHardReward(req.Sender, req.MultiplierName, req.DenomsToClaim)
}
func delegatorGenerator(req PostClaimReq) sdk.Msg {
return types.NewMsgClaimDelegatorReward(req.Sender, req.MultiplierName, req.DenomsToClaim)
}
func swapGenerator(req PostClaimReq) sdk.Msg {
return types.NewMsgClaimSwapReward(req.Sender, req.MultiplierName, req.DenomsToClaim)
}
func postClaimHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimReq) sdk.Msg) 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) {
@ -47,7 +65,7 @@ func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return return
} }
msg := types.NewMsgClaimUSDXMintingReward(requestBody.Sender, requestBody.MultiplierName) msg := msgGenerator(requestBody)
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
@ -57,7 +75,20 @@ func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
} }
func postClaimCdpVVestingHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func usdxMintingVVGenerator(req PostClaimVVestingReq) sdk.Msg {
return types.NewMsgClaimUSDXMintingRewardVVesting(req.Sender, req.Receiver, req.MultiplierName)
}
func hardVVGenerator(req PostClaimVVestingReq) sdk.Msg {
return types.NewMsgClaimHardRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim)
}
func delegatorVVGenerator(req PostClaimVVestingReq) sdk.Msg {
return types.NewMsgClaimDelegatorRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim)
}
func swapVVGenerator(req PostClaimVVestingReq) sdk.Msg {
return types.NewMsgClaimSwapRewardVVesting(req.Sender, req.Receiver, req.MultiplierName, req.DenomsToClaim)
}
func postClaimVVestingHandlerFn(cliCtx context.CLIContext, msgGenerator func(req PostClaimVVestingReq) sdk.Msg) 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) {
@ -80,139 +111,7 @@ func postClaimCdpVVestingHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return return
} }
msg := types.NewMsgClaimUSDXMintingRewardVVesting(requestBody.Sender, requestBody.Receiver, requestBody.MultiplierName) msg := msgGenerator(requestBody)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
}
}
func postClaimHardHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
return
}
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
if !requestBody.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(requestBody.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if !bytes.Equal(fromAddr, requestBody.Sender) {
rest.WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("expected: %s, got: %s", fromAddr, requestBody.Sender))
return
}
msg := types.NewMsgClaimHardReward(requestBody.Sender, requestBody.MultiplierName)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
}
}
func postClaimHardVVestingHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimVVestingReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
return
}
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
if !requestBody.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(requestBody.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if !bytes.Equal(fromAddr, requestBody.Sender) {
rest.WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("expected: %s, got: %s", fromAddr, requestBody.Sender))
return
}
msg := types.NewMsgClaimHardRewardVVesting(requestBody.Sender, requestBody.Receiver, requestBody.MultiplierName)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
}
}
func postClaimDelegatorHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
return
}
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
if !requestBody.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(requestBody.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if !bytes.Equal(fromAddr, requestBody.Sender) {
rest.WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("expected: %s, got: %s", fromAddr, requestBody.Sender))
return
}
msg := types.NewMsgClaimDelegatorReward(requestBody.Sender, requestBody.MultiplierName)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
}
}
func postClaimDelegatorVVestingHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimVVestingReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
return
}
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
if !requestBody.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(requestBody.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
if !bytes.Equal(fromAddr, requestBody.Sender) {
rest.WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("expected: %s, got: %s", fromAddr, requestBody.Sender))
return
}
msg := types.NewMsgClaimDelegatorRewardVVesting(requestBody.Sender, requestBody.Receiver, requestBody.MultiplierName)
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

@ -85,7 +85,7 @@ func (suite *GenesisTestSuite) SetupTest() {
app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)}, app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)},
app.GenesisState{hard.ModuleName: hard.ModuleCdc.MustMarshalJSON(hardGS)}, app.GenesisState{hard.ModuleName: hard.ModuleCdc.MustMarshalJSON(hardGS)},
NewCDPGenStateMulti(), NewCDPGenStateMulti(),
NewPricefeedGenStateMulti(), NewPricefeedGenStateMultiFromTime(suite.genesisTime),
) )
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: suite.genesisTime}) ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: suite.genesisTime})
@ -115,7 +115,7 @@ func (suite *GenesisTestSuite) TestPaidOutClaimsPassValidateGenesis() {
suite.Require().NoError(err) suite.Require().NoError(err)
incentiveHandler := incentive.NewHandler(suite.keeper) incentiveHandler := incentive.NewHandler(suite.keeper)
_, err = incentiveHandler(suite.ctx, incentive.NewMsgClaimHardReward(suite.addrs[0], string(incentive.Large))) _, err = incentiveHandler(suite.ctx, incentive.NewMsgClaimHardReward(suite.addrs[0], string(incentive.Large), nil))
suite.Require().NoError(err) suite.Require().NoError(err)
genState := incentive.ExportGenesis(suite.ctx, suite.keeper) genState := incentive.ExportGenesis(suite.ctx, suite.keeper)

View File

@ -25,6 +25,10 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
return handleMsgClaimDelegatorReward(ctx, k, msg) return handleMsgClaimDelegatorReward(ctx, k, msg)
case types.MsgClaimDelegatorRewardVVesting: case types.MsgClaimDelegatorRewardVVesting:
return handleMsgClaimDelegatorRewardVVesting(ctx, k, msg) return handleMsgClaimDelegatorRewardVVesting(ctx, k, msg)
case types.MsgClaimSwapReward:
return handleMsgClaimSwapReward(ctx, k, msg)
case types.MsgClaimSwapRewardVVesting:
return handleMsgClaimSwapRewardVVesting(ctx, k, msg)
default: default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
} }
@ -32,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, types.MultiplierName(msg.MultiplierName)) err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -43,7 +47,10 @@ func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types
func handleMsgClaimUSDXMintingRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingRewardVVesting) (*sdk.Result, error) { func handleMsgClaimUSDXMintingRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingRewardVVesting) (*sdk.Result, error) {
err := k.ClaimUSDXMintingRewardVVesting(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName)) if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err
}
err := k.ClaimUSDXMintingReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -54,7 +61,7 @@ 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) {
err := k.ClaimHardReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName)) err := k.ClaimHardReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -65,7 +72,10 @@ func handleMsgClaimHardReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgCla
func handleMsgClaimHardRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimHardRewardVVesting) (*sdk.Result, error) { func handleMsgClaimHardRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimHardRewardVVesting) (*sdk.Result, error) {
err := k.ClaimHardRewardVVesting(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName)) if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err
}
err := k.ClaimHardReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +86,7 @@ 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, types.MultiplierName(msg.MultiplierName)) err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Sender, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -87,7 +97,35 @@ func handleMsgClaimDelegatorReward(ctx sdk.Context, k keeper.Keeper, msg types.M
func handleMsgClaimDelegatorRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimDelegatorRewardVVesting) (*sdk.Result, error) { func handleMsgClaimDelegatorRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimDelegatorRewardVVesting) (*sdk.Result, error) {
err := k.ClaimDelegatorRewardVVesting(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName)) if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err
}
err := k.ClaimDelegatorReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil {
return nil, err
}
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
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)
if err != nil {
return nil, err
}
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
func handleMsgClaimSwapRewardVVesting(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimSwapRewardVVesting) (*sdk.Result, error) {
if err := k.ValidateIsValidatorVestingAccount(ctx, msg.Sender); err != nil {
return nil, err
}
err := k.ClaimSwapReward(ctx, msg.Sender, msg.Receiver, types.MultiplierName(msg.MultiplierName), msg.DenomsToClaim)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -0,0 +1,201 @@
package incentive_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types"
)
func (suite *HandlerTestSuite) TestPayoutDelegatorClaim() {
userAddr := suite.addrs[0]
receiverAddr := suite.addrs[1]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("ukava", 1e12))).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a delegation (need to create a validator first, which will have a self delegation)
suite.NoError(
suite.DeliverMsgCreateValidator(sdk.ValAddress(userAddr), c("ukava", 1e9)),
)
// new block required to bond validator
suite.NextBlockAfter(7 * time.Second)
// Now the delegation is bonded, accumulate some delegator rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(userAddr, receiverAddr, "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim a single denom
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(userAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := cs(c("hard", 2*7*1e6))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004786, Amount: expectedRewards},
})
// Check that claimed coins have been removed from a claim's reward
suite.DelegatorRewardEquals(userAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutDelegatorClaimSingleDenom() {
userAddr := suite.addrs[0]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("ukava", 1e12)))
incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a delegation (need to create a validator first, which will have a self delegation)
suite.NoError(
suite.DeliverMsgCreateValidator(sdk.ValAddress(userAddr), c("ukava", 1e9)),
)
// new block required to bond validator
suite.NextBlockAfter(7 * time.Second)
// Now the delegation is bonded, accumulate some delegator rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(userAddr, suite.addrs[1], "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(userAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 2*7*1e6)
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.DelegatorRewardEquals(userAddr, cs(c("hard", 2*7*1e6)))
}
func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVesting() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a delegation (need to create a validator first, which will have a self delegation)
suite.NoError(
suite.DeliverMsgCreateValidator(sdk.ValAddress(valAddr), c("ukava", 1e9)),
)
// new block required to bond validator
suite.NextBlockAfter(7 * time.Second)
// Now the delegation is bonded, accumulate some delegator rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(valAddr, "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("hard", 2*7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)},
})
// Check that each claim reward coin's amount has been reset to 0
suite.DelegatorRewardEquals(valAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutDelegatorClaimVVestingSingleDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleDelegatorRewardPeriod(types.BondDenom, cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a delegation (need to create a validator first, which will have a self delegation)
suite.NoError(
suite.DeliverMsgCreateValidator(sdk.ValAddress(valAddr), c("ukava", 1e9)),
)
// new block required to bond validator
suite.NextBlockAfter(7 * time.Second)
// Now the delegation is bonded, accumulate some delegator rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorReward(valAddr, "large", []string{"swap"}),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimDelegatorRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 2*7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004786, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.DelegatorRewardEquals(valAddr, cs(c("hard", 2*7*1e6)))
}

View File

@ -0,0 +1,195 @@
package incentive_test
import (
"time"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types"
)
func (suite *HandlerTestSuite) TestPayoutHardClaim() {
userAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("bnb", 1e12))).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a deposit and borrow
suite.NoError(suite.DeliverHardMsgDeposit(userAddr, cs(c("bnb", 1e11))))
suite.NoError(suite.DeliverHardMsgBorrow(userAddr, cs(c("bnb", 1e10))))
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(userAddr, receiverAddr, "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim a single denom
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(userAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := cs(c("hard", 2*7*1e6))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards},
})
// Check that claimed coins have been removed from a claim's reward
suite.HardRewardEquals(userAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutHardClaimSingleDenom() {
userAddr := suite.addrs[0]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("bnb", 1e12)))
incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a deposit and borrow
suite.NoError(suite.DeliverHardMsgDeposit(userAddr, cs(c("bnb", 1e11))))
suite.NoError(suite.DeliverHardMsgBorrow(userAddr, cs(c("bnb", 1e10))))
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(userAddr, suite.addrs[1], "large", []string{"swap"}),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(userAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 2*7*1e6)
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.HardRewardEquals(userAddr, cs(c("hard", 2*7*1e6)))
}
func (suite *HandlerTestSuite) TestPayoutHardClaimVVesting() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("bnb", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a deposit and borrow
suite.NoError(suite.DeliverHardMsgDeposit(valAddr, cs(c("bnb", 1e11))))
suite.NoError(suite.DeliverHardMsgBorrow(valAddr, cs(c("bnb", 1e10))))
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(valAddr, "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("hard", 2*7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that each claim reward coin's amount has been reset to 0
suite.HardRewardEquals(valAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutHardClaimVVestingSingleDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("bnb", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleSupplyRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6))).
WithSimpleBorrowRewardPeriod("bnb", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// create a deposit and borrow
suite.NoError(suite.DeliverHardMsgDeposit(valAddr, cs(c("bnb", 1e11))))
suite.NoError(suite.DeliverHardMsgBorrow(valAddr, cs(c("bnb", 1e10))))
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimHardReward(valAddr, "large", []string{"swap"}),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimHardRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 2*7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.HardRewardEquals(valAddr, cs(c("hard", 2*7*1e6)))
}

View File

@ -0,0 +1,298 @@
package incentive_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"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/types"
"github.com/kava-labs/kava/x/kavadist"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
// Test suite used for all keeper tests
type HandlerTestSuite struct {
testutil.IntegrationTester
// TODO remove these
keeper keeper.Keeper
hardKeeper hardkeeper.Keeper
cdpKeeper cdpkeeper.Keeper
handler sdk.Handler
genesisTime time.Time
addrs []sdk.AccAddress
}
func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}
// SetupTest is run automatically before each suite test
func (suite *HandlerTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
_, suite.addrs = app.GeneratePrivKeyAddressPairs(5)
suite.genesisTime = time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
}
func (suite *HandlerTestSuite) SetupApp() {
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})
}
type genesisBuilder interface {
BuildMarshalled() app.GenesisState
}
func (suite *HandlerTestSuite) SetupWithGenState(builders ...genesisBuilder) {
suite.SetupApp()
builtGenStates := []app.GenesisState{
NewStakingGenesisState(),
NewPricefeedGenStateMultiFromTime(suite.genesisTime),
NewCDPGenStateMulti(),
NewHardGenStateMulti(suite.genesisTime).BuildMarshalled(),
NewSwapGenesisState(),
}
for _, builder := range builders {
builtGenStates = append(builtGenStates, builder.BuildMarshalled())
}
suite.App.InitializeFromGenesisStatesWithTime(
suite.genesisTime,
builtGenStates...,
)
}
// for the purposes of incentive module. A validator vesting account only needs to exist, and have enough balance to delegate/or supply.
func (suite *HandlerTestSuite) NewValidatorVestingAccountWithBalance(address sdk.AccAddress, spendableBalance sdk.Coins) *validatorvesting.ValidatorVestingAccount {
bacc := auth.NewBaseAccount(address, spendableBalance, nil, 0, 0)
// vesting coins set to nil and vesting end time set to genesis full base account balance should be spendable
bva, err := vesting.NewBaseVestingAccount(bacc, nil, suite.genesisTime.Unix())
if err != nil {
panic(err.Error())
}
// vesting start time set to genesis and no vesting periods
return validatorvesting.NewValidatorVestingAccountRaw(bva, suite.genesisTime.Unix(), nil, sdk.ConsAddress{}, nil, 90)
}
// authBuilder returns a new auth genesis builder with a full kavadist module account.
func (suite *HandlerTestSuite) authBuilder() app.AuthGenesisBuilder {
return app.NewAuthGenesisBuilder().
WithSimpleModuleAccount(kavadist.ModuleName, cs(c(types.USDXMintingRewardDenom, 1e18), c("hard", 1e18), c("swap", 1e18)))
}
// incentiveBuilder returns a new incentive genesis builder with a genesis time and multipliers set
func (suite *HandlerTestSuite) incentiveBuilder() testutil.IncentiveGenesisBuilder {
return testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime).
WithMultipliers(types.Multipliers{
types.NewMultiplier(types.MultiplierName("small"), 1, d("0.2")),
types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0")),
})
}
func (suite *HandlerTestSuite) TestPayoutSwapClaim() {
userAddr := suite.addrs[0]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("ukava", 1e12), c("busd", 1e12)))
incentBuilder := suite.incentiveBuilder().
WithSimpleSwapRewardPeriod("busd/ukava", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// deposit into a swap pool
suite.NoError(
suite.DeliverSwapMsgDeposit(userAddr, c("ukava", 1e9), c("busd", 1e9), d("1.0")),
)
// accumulate some swap rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(userAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := cs(c("swap", 7*1e6), c("hard", 7*1e6))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards},
})
// Check that each claim reward coin's amount has been reset to 0
suite.SwapRewardEquals(userAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutSwapClaimSingleDenom() {
userAddr := suite.addrs[0]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("ukava", 1e12), c("busd", 1e12)))
incentBuilder := suite.incentiveBuilder().
WithSimpleSwapRewardPeriod("busd/ukava", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// deposit into a swap pool
suite.NoError(
suite.DeliverSwapMsgDeposit(userAddr, c("ukava", 1e9), c("busd", 1e9), d("1.0")),
)
// accumulate some swap rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(userAddr, suite.addrs[1], "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(userAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 7*1e6)
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.SwapRewardEquals(userAddr, cs(c("hard", 7*1e6)))
}
func (suite *HandlerTestSuite) TestPayoutSwapClaimVVesting() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12), c("busd", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleSwapRewardPeriod("busd/ukava", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// deposit into a swap pool
suite.NoError(
suite.DeliverSwapMsgDeposit(valAddr, c("ukava", 1e9), c("busd", 1e9), d("1.0")),
)
// accumulate some swap rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(valAddr, "large", nil),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, "large", nil),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := cs(c("hard", 7*1e6), c("swap", 7*1e6))
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards},
})
// Check that each claim reward coin's amount has been reset to 0
suite.SwapRewardEquals(valAddr, nil)
}
func (suite *HandlerTestSuite) TestPayoutSwapClaimVVestingSingleDenom() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("ukava", 1e12), c("busd", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleSwapRewardPeriod("busd/ukava", cs(c("hard", 1e6), c("swap", 1e6)))
suite.SetupWithGenState(authBulder, incentBuilder)
// deposit into a swap pool
suite.NoError(
suite.DeliverSwapMsgDeposit(valAddr, c("ukava", 1e9), c("busd", 1e9), d("1.0")),
)
// accumulate some swap rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapReward(valAddr, "large", []string{"swap"}),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimSwapRewardVVesting(valAddr, receiverAddr, "large", []string{"swap"}),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c("swap", 7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that claimed coins have been removed from a claim's reward
suite.SwapRewardEquals(valAddr, cs(c("hard", 7*1e6)))
}

View File

@ -1,121 +0,0 @@
package incentive_test
import (
"testing"
"time"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/incentive"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist"
)
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
type HandlerTestSuite struct {
suite.Suite
ctx sdk.Context
app app.TestApp
handler sdk.Handler
keeper incentive.Keeper
addrs []sdk.AccAddress
}
func (suite *HandlerTestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
keeper := tApp.GetIncentiveKeeper()
// Set up genesis state and initialize
_, addrs := app.GeneratePrivKeyAddressPairs(3)
coins := []sdk.Coins{}
for j := 0; j < 3; j++ {
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
}
authGS := app.NewAuthGenState(addrs, coins)
incentiveGS := incentive.NewGenesisState(
incentive.NewParams(
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), cs(c("ukava", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "bnb", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), cs(c("ukava", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "ukava", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), cs(c("ukava", 122354)))},
incentive.MultiRewardPeriods{incentive.NewMultiRewardPeriod(true, "btcb/usdx", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), cs(c("ukava", 122354)))},
incentive.Multipliers{incentive.NewMultiplier(incentive.MultiplierName("small"), 1, d("0.25")), incentive.NewMultiplier(incentive.MultiplierName("large"), 12, d("1.0"))},
time.Date(2025, 12, 15, 14, 0, 0, 0, time.UTC),
),
incentive.DefaultGenesisAccumulationTimes,
incentive.DefaultGenesisAccumulationTimes,
incentive.DefaultGenesisAccumulationTimes,
incentive.DefaultGenesisAccumulationTimes,
incentive.DefaultGenesisAccumulationTimes,
incentive.DefaultUSDXClaims,
incentive.DefaultHardClaims,
incentive.DefaultDelegatorClaims,
incentive.DefaultSwapClaims,
)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)}, NewCDPGenStateMulti(), NewPricefeedGenStateMulti())
suite.addrs = addrs
suite.handler = incentive.NewHandler(keeper)
suite.keeper = keeper
suite.app = tApp
suite.ctx = ctx
}
func (suite *HandlerTestSuite) TestMsgUSDXMintingClaimReward() {
suite.addUSDXMintingClaim()
msg := incentive.NewMsgClaimUSDXMintingReward(suite.addrs[0], "small")
res, err := suite.handler(suite.ctx, msg)
suite.NoError(err)
suite.Require().NotNil(res)
}
func (suite *HandlerTestSuite) TestMsgHardClaimReward() {
suite.addHardLiquidityProviderClaim()
msg := incentive.NewMsgClaimHardReward(suite.addrs[0], "small")
res, err := suite.handler(suite.ctx, msg)
suite.NoError(err)
suite.Require().NotNil(res)
}
func (suite *HandlerTestSuite) addHardLiquidityProviderClaim() {
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
suite.Require().NoError(err)
rewardPeriod := types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())}
multiRewardIndex := types.NewMultiRewardIndex("bnb-s", rewardPeriod)
multiRewardIndexes := types.MultiRewardIndexes{multiRewardIndex}
c1 := incentive.NewHardLiquidityProviderClaim(suite.addrs[0], cs(c("ukava", 1000000)), multiRewardIndexes, multiRewardIndexes)
suite.NotPanics(func() {
suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, c1)
})
}
func (suite *HandlerTestSuite) addUSDXMintingClaim() {
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
suite.Require().NoError(err)
c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())})
suite.NotPanics(func() {
suite.keeper.SetUSDXMintingClaim(suite.ctx, c1)
})
}
// TODO: add tests
func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}
// Avoid cluttering test cases with long function names
func i(in int64) sdk.Int { return sdk.NewInt(in) }
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }

View File

@ -0,0 +1,96 @@
package incentive_test
import (
"time"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types"
)
func (suite *HandlerTestSuite) TestPayoutUSDXClaim() {
userAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
authBulder := suite.authBuilder().
WithSimpleAccount(userAddr, cs(c("bnb", 1e12))).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleUSDXRewardPeriod("bnb-a", c(types.USDXMintingRewardDenom, 1e6))
suite.SetupWithGenState(authBulder, incentBuilder)
// mint some usdx
suite.DeliverMsgCreateCDP(userAddr, c("bnb", 1e9), c("usdx", 1e7), "bnb-a")
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(userAddr)
// Check rewards cannot be claimed by vvesting claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimUSDXMintingRewardVVesting(userAddr, receiverAddr, "large"),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim a single denom
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimUSDXMintingReward(userAddr, "large"),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := cs(c(types.USDXMintingRewardDenom, 7*1e6))
suite.BalanceEquals(userAddr, preClaimBal.Add(expectedRewards...))
suite.VestingPeriodsEqual(userAddr, vesting.Periods{
{Length: 33004793, Amount: expectedRewards},
})
// Check that claimed coins have been removed from a claim's reward
suite.USDXRewardEquals(userAddr, c(types.USDXMintingRewardDenom, 0))
}
func (suite *HandlerTestSuite) TestPayoutUSDXClaimVVesting() {
valAddr, receiverAddr := suite.addrs[0], suite.addrs[1]
vva := suite.NewValidatorVestingAccountWithBalance(valAddr, cs(c("bnb", 1e12)))
authBulder := suite.authBuilder().
WithAccounts(vva).
WithSimpleAccount(receiverAddr, nil)
incentBuilder := suite.incentiveBuilder().
WithSimpleUSDXRewardPeriod("bnb-a", c(types.USDXMintingRewardDenom, 1e6))
suite.SetupWithGenState(authBulder, incentBuilder)
// mint some usdx
suite.DeliverMsgCreateCDP(valAddr, c("bnb", 1e9), c("usdx", 1e7), "bnb-a")
// accumulate some rewards
suite.NextBlockAfter(7 * time.Second)
preClaimBal := suite.GetBalance(receiverAddr)
// Check rewards cannot be claimed by normal claim msgs
err := suite.DeliverIncentiveMsg(
types.NewMsgClaimUSDXMintingReward(valAddr, "large"),
)
suite.ErrorIs(err, types.ErrInvalidAccountType)
// Claim rewards
err = suite.DeliverIncentiveMsg(
types.NewMsgClaimUSDXMintingRewardVVesting(valAddr, receiverAddr, "large"),
)
suite.NoError(err)
// Check rewards were paid out
expectedRewards := c(types.USDXMintingRewardDenom, 7*1e6)
suite.BalanceEquals(receiverAddr, preClaimBal.Add(expectedRewards))
suite.VestingPeriodsEqual(receiverAddr, vesting.Periods{
{Length: 33004793, Amount: cs(expectedRewards)},
})
// Check that each claim reward coin's amount has been reset to 0
suite.USDXRewardEquals(valAddr, c(types.USDXMintingRewardDenom, 0))
}

View File

@ -4,12 +4,21 @@ import (
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
"github.com/kava-labs/kava/x/swap"
) )
// Avoid cluttering test cases with long function names
func i(in int64) sdk.Int { return sdk.NewInt(in) }
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
func NewCDPGenStateMulti() app.GenesisState { func NewCDPGenStateMulti() app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
@ -99,120 +108,92 @@ func NewCDPGenStateMulti() app.GenesisState {
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
} }
func NewPricefeedGenStateMulti() app.GenesisState { func NewPricefeedGenStateMultiFromTime(t time.Time) app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ Markets: []pricefeed.Market{
{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "busd:usd", BaseAsset: "busd", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "busd:usd", BaseAsset: "busd", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "zzz:usd", BaseAsset: "zzz", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
}, },
}, },
PostedPrices: []pricefeed.PostedPrice{ PostedPrices: []pricefeed.PostedPrice{
{
MarketID: "kava:usd",
OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("2.00"),
Expiry: t.Add(1 * time.Hour),
},
{ {
MarketID: "btc:usd", MarketID: "btc:usd",
OracleAddress: sdk.AccAddress{}, OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("8000.00"), Price: sdk.MustNewDecFromStr("8000.00"),
Expiry: time.Now().Add(1 * time.Hour), Expiry: t.Add(1 * time.Hour),
}, },
{ {
MarketID: "xrp:usd", MarketID: "xrp:usd",
OracleAddress: sdk.AccAddress{}, OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("0.25"), Price: sdk.MustNewDecFromStr("0.25"),
Expiry: time.Now().Add(1 * time.Hour), Expiry: t.Add(1 * time.Hour),
}, },
{ {
MarketID: "bnb:usd", MarketID: "bnb:usd",
OracleAddress: sdk.AccAddress{}, OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("17.25"), Price: sdk.MustNewDecFromStr("17.25"),
Expiry: time.Now().Add(1 * time.Hour), Expiry: t.Add(1 * time.Hour),
}, },
{ {
MarketID: "busd:usd", MarketID: "busd:usd",
OracleAddress: sdk.AccAddress{}, OracleAddress: sdk.AccAddress{},
Price: sdk.OneDec(), Price: sdk.OneDec(),
Expiry: time.Now().Add(1 * time.Hour), Expiry: t.Add(1 * time.Hour),
},
{
MarketID: "zzz:usd",
OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("2.00"),
Expiry: t.Add(1 * time.Hour),
}, },
}, },
} }
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
} }
// func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ...incentive.RewardPeriod) app.GenesisState { func NewHardGenStateMulti(genTime time.Time) testutil.HardGenesisBuilder {
// var accumulationTimes incentive.GenesisAccumulationTimes kavaMM := testutil.NewStandardMoneyMarket("ukava")
// for _, rp := range rewardPeriods { kavaMM.SpotMarketID = "kava:usd"
// accumulationTimes = append( btcMM := testutil.NewStandardMoneyMarket("btcb")
// accumulationTimes, btcMM.SpotMarketID = "btc:usd"
// incentive.NewGenesisAccumulationTime(
// rp.CollateralType,
// previousAccumTime,
// ),
// )
// }
// genesis := incentive.NewGenesisState(
// incentive.NewParams(
// rewardPeriods,
// types.MultiRewardPeriods{},
// types.MultiRewardPeriods{},
// types.MultiRewardPeriods{},
// types.MultiRewardPeriods{},
// incentive.Multipliers{
// incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
// incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
// },
// endTime,
// ),
// accumulationTimes,
// accumulationTimes,
// accumulationTimes,
// incentive.DefaultGenesisAccumulationTimes,
// incentive.DefaultUSDXClaims,
// incentive.DefaultHardClaims,
// )
// return app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(genesis)}
// }
func NewCDPGenStateHighInterest() app.GenesisState { builder := testutil.NewHardGenesisBuilder().WithGenesisTime(genTime).
cdpGenesis := cdp.GenesisState{ WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("usdx")).
Params: cdp.Params{ WithInitializedMoneyMarket(kavaMM).
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000), WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("bnb")).
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, WithInitializedMoneyMarket(btcMM).
SurplusAuctionLot: cdp.DefaultSurplusLot, WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("xrp")).
DebtAuctionThreshold: cdp.DefaultDebtThreshold, WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("zzz"))
DebtAuctionLot: cdp.DefaultDebtLot, return builder
CollateralParams: cdp.CollateralParams{ }
{
Denom: "bnb", func NewStakingGenesisState() app.GenesisState {
Type: "bnb-a", genState := staking.DefaultGenesisState()
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), genState.Params.BondDenom = "ukava"
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000), return app.GenesisState{
StabilityFee: sdk.MustNewDecFromStr("1.000000051034942716"), // 500% APR staking.ModuleName: staking.ModuleCdc.MustMarshalJSON(genState),
LiquidationPenalty: d("0.05"), }
AuctionSize: i(50000000000), }
Prefix: 0x22,
SpotMarketID: "bnb:usd", func NewSwapGenesisState() app.GenesisState {
LiquidationMarketID: "bnb:usd", genesis := swap.NewGenesisState(
ConversionFactor: i(8), swap.NewParams(
}, swap.NewAllowedPools(swap.NewAllowedPool("busd", "ukava")),
}, d("0.0"),
DebtParam: cdp.DebtParam{ ),
Denom: "usdx", )
ReferenceAsset: "usd", return app.GenesisState{
ConversionFactor: i(6), swap.ModuleName: swap.ModuleCdc.MustMarshalJSON(genesis),
DebtFloor: i(10000000), }
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
},
TotalPrincipals: cdp.GenesisTotalPrincipals{
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
},
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
} }

View File

@ -8,6 +8,7 @@ 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"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
@ -23,7 +24,7 @@ func TestRiskyCDPsAccumulateRewards(t *testing.T) {
collateralType := "bnb-a" collateralType := "bnb-a"
rewardsPerSecond := c(types.USDXMintingRewardDenom, 1_000_000) rewardsPerSecond := c(types.USDXMintingRewardDenom, 1_000_000)
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(genesisTime). WithGenesisTime(genesisTime).
WithSimpleUSDXRewardPeriod(collateralType, rewardsPerSecond) WithSimpleUSDXRewardPeriod(collateralType, rewardsPerSecond)

239
x/incentive/keeper/claim.go Normal file
View File

@ -0,0 +1,239 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/incentive/types"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
// 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.
func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetUSDXMintingClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
claim, err := k.SynchronizeUSDXMintingClaim(ctx, claim)
if err != nil {
return err
}
rewardAmount := claim.Reward.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, sdk.NewCoins(rewardCoin), length)
if err != nil {
return err
}
k.ZeroUSDXMintingClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetType()),
),
)
return nil
}
// 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.
func (k Keeper) ClaimHardReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error {
_, found := k.GetHardLiquidityProviderClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
k.SynchronizeHardLiquidityProviderClaim(ctx, owner)
syncedClaim, found := k.GetHardLiquidityProviderClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}
// remove claimed coins (NOT reward coins)
syncedClaim.Reward = syncedClaim.Reward.Sub(claimingCoins)
k.SetHardLiquidityProviderClaim(ctx, syncedClaim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claimingCoins.String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
// 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.
func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error {
claim, found := k.GetDelegatorClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
syncedClaim, err := k.SynchronizeDelegatorClaim(ctx, claim)
if err != nil {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}
// remove claimed coins (NOT reward coins)
syncedClaim.Reward = syncedClaim.Reward.Sub(claimingCoins)
k.SetDelegatorClaim(ctx, syncedClaim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claimingCoins.String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
// 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.
func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName, denomsToClaim []string) error {
_, found := k.GetSwapClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
syncedClaim, found := k.GetSynchronizedSwapClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
claimingCoins := types.FilterCoins(syncedClaim.Reward, denomsToClaim)
rewardCoins := types.MultiplyCoins(claimingCoins, multiplier.Factor)
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}
// remove claimed coins (NOT reward coins)
syncedClaim.Reward = syncedClaim.Reward.Sub(claimingCoins)
k.SetSwapClaim(ctx, syncedClaim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claimingCoins.String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
func (k Keeper) ValidateIsValidatorVestingAccount(ctx sdk.Context, address sdk.AccAddress) error {
acc := k.accountKeeper.GetAccount(ctx, address)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address not found: %s", address)
}
_, ok := acc.(*validatorvesting.ValidatorVestingAccount)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "account is not validator vesting account, %s", address)
}
return nil
}

View File

@ -0,0 +1,81 @@
package keeper_test
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/kava-labs/kava/x/incentive/types"
)
// ClaimTests runs unit tests for the keeper Claim methods
type ClaimTests struct {
unitTester
}
func TestClaim(t *testing.T) {
suite.Run(t, new(ClaimTests))
}
func (suite *ClaimTests) ErrorIs(err, target error) bool {
return suite.Truef(errors.Is(err, target), "err didn't match: %s, it was: %s", target, err)
}
func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() {
subspace := &fakeParamSubspace{
params: types.Params{
ClaimMultipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, d("0.2")),
},
},
}
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
claim := types.DelegatorClaim{
BaseMultiClaim: types.BaseMultiClaim{
Owner: arbitraryAddress(),
},
}
suite.storeDelegatorClaim(claim)
// multiplier not in params
err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, types.Large, nil)
suite.ErrorIs(err, types.ErrInvalidMultiplier)
// invalid multiplier name
err = suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, "", nil)
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)
}
func (suite *ClaimTests) TestCannotClaimAfterEndTime() {
endTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
subspace := &fakeParamSubspace{
params: types.Params{
ClaimMultipliers: types.Multipliers{
types.NewMultiplier(types.Small, 1, d("0.2")),
},
ClaimEnd: endTime,
},
}
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
suite.ctx = suite.ctx.WithBlockTime(endTime.Add(time.Nanosecond))
claim := types.DelegatorClaim{
BaseMultiClaim: types.BaseMultiClaim{
Owner: arbitraryAddress(),
},
}
suite.storeDelegatorClaim(claim)
err := suite.keeper.ClaimDelegatorReward(suite.ctx, claim.Owner, claim.Owner, types.Small, nil)
suite.ErrorIs(err, types.ErrClaimExpired)
}

View File

@ -9,16 +9,10 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
committeetypes "github.com/kava-labs/kava/x/committee/types" committeetypes "github.com/kava-labs/kava/x/committee/types"
"github.com/kava-labs/kava/x/hard" "github.com/kava-labs/kava/x/incentive/testutil"
hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
) )
const (
oneYear time.Duration = time.Hour * 24 * 365
)
// Avoid cluttering test cases with long function names // Avoid cluttering test cases with long function names
func i(in int64) sdk.Int { return sdk.NewInt(in) } func i(in int64) sdk.Int { return sdk.NewInt(in) }
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
@ -168,19 +162,19 @@ func NewPricefeedGenStateMultiFromTime(t time.Time) app.GenesisState {
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
} }
func NewHardGenStateMulti(genTime time.Time) HardGenesisBuilder { func NewHardGenStateMulti(genTime time.Time) testutil.HardGenesisBuilder {
kavaMM := NewStandardMoneyMarket("ukava") kavaMM := testutil.NewStandardMoneyMarket("ukava")
kavaMM.SpotMarketID = "kava:usd" kavaMM.SpotMarketID = "kava:usd"
btcMM := NewStandardMoneyMarket("btcb") btcMM := testutil.NewStandardMoneyMarket("btcb")
btcMM.SpotMarketID = "btc:usd" btcMM.SpotMarketID = "btc:usd"
builder := NewHardGenesisBuilder().WithGenesisTime(genTime). builder := testutil.NewHardGenesisBuilder().WithGenesisTime(genTime).
WithInitializedMoneyMarket(NewStandardMoneyMarket("usdx")). WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("usdx")).
WithInitializedMoneyMarket(kavaMM). WithInitializedMoneyMarket(kavaMM).
WithInitializedMoneyMarket(NewStandardMoneyMarket("bnb")). WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("bnb")).
WithInitializedMoneyMarket(btcMM). WithInitializedMoneyMarket(btcMM).
WithInitializedMoneyMarket(NewStandardMoneyMarket("xrp")). WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("xrp")).
WithInitializedMoneyMarket(NewStandardMoneyMarket("zzz")) WithInitializedMoneyMarket(testutil.NewStandardMoneyMarket("zzz"))
return builder return builder
} }
@ -212,162 +206,3 @@ func NewCommitteeGenesisState(members []sdk.AccAddress) app.GenesisState {
committeetypes.ModuleName: committeetypes.ModuleCdc.MustMarshalJSON(genState), committeetypes.ModuleName: committeetypes.ModuleCdc.MustMarshalJSON(genState),
} }
} }
// IncentiveGenesisBuilder is a tool for creating an incentive genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type IncentiveGenesisBuilder struct {
types.GenesisState
genesisTime time.Time
}
func NewIncentiveGenesisBuilder() IncentiveGenesisBuilder {
return IncentiveGenesisBuilder{
GenesisState: types.DefaultGenesisState(),
genesisTime: time.Time{},
}
}
func (builder IncentiveGenesisBuilder) Build() types.GenesisState {
return builder.GenesisState
}
func (builder IncentiveGenesisBuilder) BuildMarshalled() app.GenesisState {
return app.GenesisState{
types.ModuleName: types.ModuleCdc.MustMarshalJSON(builder.Build()),
}
}
func (builder IncentiveGenesisBuilder) WithGenesisTime(time time.Time) IncentiveGenesisBuilder {
builder.genesisTime = time
builder.Params.ClaimEnd = time.Add(5 * oneYear)
return builder
}
func (builder IncentiveGenesisBuilder) WithInitializedBorrowRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.HardBorrowRewardPeriods = append(builder.Params.HardBorrowRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.HardBorrowAccumulationTimes = append(builder.HardBorrowAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleBorrowRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedBorrowRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.HardSupplyRewardPeriods = append(builder.Params.HardSupplyRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.HardSupplyAccumulationTimes = append(builder.HardSupplyAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleSupplyRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedSupplyRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedDelegatorRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.DelegatorRewardPeriods = append(builder.Params.DelegatorRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.DelegatorAccumulationTimes = append(builder.DelegatorAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleDelegatorRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedDelegatorRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedUSDXRewardPeriod(period types.RewardPeriod) IncentiveGenesisBuilder {
builder.Params.USDXMintingRewardPeriods = append(builder.Params.USDXMintingRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.USDXAccumulationTimes = append(builder.USDXAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleUSDXRewardPeriod(ctype string, rewardsPerSecond sdk.Coin) IncentiveGenesisBuilder {
return builder.WithInitializedUSDXRewardPeriod(types.NewRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.Multipliers) IncentiveGenesisBuilder {
builder.Params.ClaimMultipliers = multipliers
return builder
}
// HardGenesisBuilder is a tool for creating a hard genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type HardGenesisBuilder struct {
hardtypes.GenesisState
genesisTime time.Time
}
func NewHardGenesisBuilder() HardGenesisBuilder {
return HardGenesisBuilder{
GenesisState: hardtypes.DefaultGenesisState(),
}
}
func (builder HardGenesisBuilder) Build() hardtypes.GenesisState {
return builder.GenesisState
}
func (builder HardGenesisBuilder) BuildMarshalled() app.GenesisState {
return app.GenesisState{
hardtypes.ModuleName: hardtypes.ModuleCdc.MustMarshalJSON(builder.Build()),
}
}
func (builder HardGenesisBuilder) WithGenesisTime(genTime time.Time) HardGenesisBuilder {
builder.genesisTime = genTime
return builder
}
func (builder HardGenesisBuilder) WithInitializedMoneyMarket(market hard.MoneyMarket) HardGenesisBuilder {
builder.Params.MoneyMarkets = append(builder.Params.MoneyMarkets, market)
builder.PreviousAccumulationTimes = append(
builder.PreviousAccumulationTimes,
hardtypes.NewGenesisAccumulationTime(market.Denom, builder.genesisTime, sdk.OneDec(), sdk.OneDec()),
)
return builder
}
func (builder HardGenesisBuilder) WithMinBorrow(minUSDValue sdk.Dec) HardGenesisBuilder {
builder.Params.MinimumBorrowUSDValue = minUSDValue
return builder
}
func NewStandardMoneyMarket(denom string) hardtypes.MoneyMarket {
return hardtypes.NewMoneyMarket(
denom,
hard.NewBorrowLimit(
false,
sdk.NewDec(1e15),
d("0.6"),
),
denom+":usd",
i(1e6),
hard.NewInterestRateModel(d("0.05"), d("2"), d("0.8"), d("10")),
d("0.05"),
sdk.ZeroDec(),
)
}

View File

@ -23,367 +23,6 @@ const (
PaymentHour = 14 PaymentHour = 14
) )
// ClaimUSDXMintingReward sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetUSDXMintingClaim(ctx, addr)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
claim, err := k.SynchronizeUSDXMintingClaim(ctx, claim)
if err != nil {
return err
}
rewardAmount := claim.Reward.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(rewardCoin), length)
if err != nil {
return err
}
k.ZeroUSDXMintingClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, claim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetType()),
),
)
return nil
}
// ClaimUSDXMintingReward sends the reward amount to the input receiver address and zero's out the claim in the store
func (k Keeper) ClaimUSDXMintingRewardVVesting(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetUSDXMintingClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
acc := k.accountKeeper.GetAccount(ctx, owner)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address not found: %s", owner)
}
_, ok := acc.(*validatorvesting.ValidatorVestingAccount)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "owner account must be validator vesting account %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
claim, err := k.SynchronizeUSDXMintingClaim(ctx, claim)
if err != nil {
return err
}
rewardAmount := claim.Reward.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, sdk.NewCoins(rewardCoin), length)
if err != nil {
return err
}
k.ZeroUSDXMintingClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, claim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetType()),
),
)
return nil
}
// ClaimHardReward sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimHardReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
_, found := k.GetHardLiquidityProviderClaim(ctx, addr)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
k.SynchronizeHardLiquidityProviderClaim(ctx, addr)
claim, found := k.GetHardLiquidityProviderClaim(ctx, addr)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
}
var rewardCoins sdk.Coins
for _, coin := range claim.Reward {
rewardAmount := coin.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
continue
}
rewardCoins = append(rewardCoins, sdk.NewCoin(coin.Denom, rewardAmount))
}
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, rewardCoins, length)
if err != nil {
return err
}
k.ZeroHardLiquidityProviderClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, claim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimType, claim.GetType()),
),
)
return nil
}
// ClaimHardRewardVVesting sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimHardRewardVVesting(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
_, found := k.GetHardLiquidityProviderClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
acc := k.accountKeeper.GetAccount(ctx, owner)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address not found: %s", owner)
}
_, ok := acc.(*validatorvesting.ValidatorVestingAccount)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "owner account must be validator vesting account %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
k.SynchronizeHardLiquidityProviderClaim(ctx, owner)
claim, found := k.GetHardLiquidityProviderClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
var rewardCoins sdk.Coins
for _, coin := range claim.Reward {
rewardAmount := coin.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
continue
}
rewardCoins = append(rewardCoins, sdk.NewCoin(coin.Denom, rewardAmount))
}
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}
k.ZeroHardLiquidityProviderClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, claim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimType, claim.GetType()),
),
)
return nil
}
// ClaimDelegatorReward sends the reward amount to the input address and zero's out the delegator claim in the store
func (k Keeper) ClaimDelegatorReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetDelegatorClaim(ctx, addr)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
syncedClaim, err := k.SynchronizeDelegatorClaim(ctx, claim)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
}
var rewardCoins sdk.Coins
for _, coin := range syncedClaim.Reward {
rewardAmount := coin.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
continue
}
rewardCoins = append(rewardCoins, sdk.NewCoin(coin.Denom, rewardAmount))
}
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, rewardCoins, length)
if err != nil {
return err
}
k.ZeroDelegatorClaim(ctx, syncedClaim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, syncedClaim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, syncedClaim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
// ClaimDelegatorRewardVVesting sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimDelegatorRewardVVesting(ctx sdk.Context, owner, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetDelegatorClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
acc := k.accountKeeper.GetAccount(ctx, owner)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address not found: %s", owner)
}
_, ok := acc.(*validatorvesting.ValidatorVestingAccount)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "owner account must be validator vesting account %s", owner)
}
multiplier, found := k.GetMultiplier(ctx, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
claimEnd := k.GetClaimEnd(ctx)
if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
syncedClaim, err := k.SynchronizeDelegatorClaim(ctx, claim)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}
var rewardCoins sdk.Coins
for _, coin := range syncedClaim.Reward {
rewardAmount := coin.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
continue
}
rewardCoins = append(rewardCoins, sdk.NewCoin(coin.Denom, rewardAmount))
}
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}
k.ZeroDelegatorClaim(ctx, syncedClaim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, syncedClaim.GetOwner().String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, syncedClaim.GetReward().String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
// SendTimeLockedCoinsToAccount sends time-locked coins from the input module account to the recipient. If the recipients account is not a vesting account and the input length is greater than zero, the recipient account is converted to a periodic vesting account and the coins are added to the vesting balance as a vesting period with the input length. // SendTimeLockedCoinsToAccount sends time-locked coins from the input module account to the recipient. If the recipients account is not a vesting account and the input length is greater than zero, the recipient account is converted to a periodic vesting account and the coins are added to the vesting balance as a vesting period with the input length.
func (k Keeper) SendTimeLockedCoinsToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error { func (k Keeper) SendTimeLockedCoinsToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
macc := k.supplyKeeper.GetModuleAccount(ctx, senderModule) macc := k.supplyKeeper.GetModuleAccount(ctx, senderModule)

View File

@ -7,7 +7,6 @@ import (
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/auth/vesting"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
@ -18,12 +17,11 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper" cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/hard"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper" hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"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"
) )
// Test suite used for all keeper tests // Test suite used for all keeper tests
@ -61,7 +59,7 @@ func (suite *PayoutTestSuite) SetupApp() {
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})
} }
func (suite *PayoutTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder IncentiveGenesisBuilder, hardBuilder HardGenesisBuilder) { func (suite *PayoutTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder, hardBuilder testutil.HardGenesisBuilder) {
suite.SetupApp() suite.SetupApp()
suite.app.InitializeFromGenesisStatesWithTime( suite.app.InitializeFromGenesisStatesWithTime(
@ -84,674 +82,6 @@ func (suite *PayoutTestSuite) getModuleAccount(name string) supplyexported.Modul
return sk.GetModuleAccount(suite.ctx, name) return sk.GetModuleAccount(suite.ctx, name)
} }
func (suite *PayoutTestSuite) TestPayoutUSDXMintingClaim() {
type args struct {
ctype string
rewardsPerSecond sdk.Coin
initialCollateral sdk.Coin
initialPrincipal sdk.Coin
multipliers types.Multipliers
multiplier types.MultiplierName
timeElapsed int
expectedBalance sdk.Coins
expectedPeriods vesting.Periods
isPeriodicVestingAccount bool
}
type errArgs struct {
expectPass bool
contains string
}
type test struct {
name string
args args
errArgs errArgs
}
testCases := []test{
{
"valid 1 day",
args{
ctype: "bnb-a",
rewardsPerSecond: c("ukava", 122354),
initialCollateral: c("bnb", 1000000000000),
initialPrincipal: c("usdx", 10000000000),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedBalance: cs(c("usdx", 10000000000), c("ukava", 10571385600)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("ukava", 10571385600))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"invalid zero rewards",
args{
ctype: "bnb-a",
rewardsPerSecond: c("ukava", 0),
initialCollateral: c("bnb", 1000000000000),
initialPrincipal: c("usdx", 10000000000),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedBalance: cs(c("usdx", 10000000000)),
expectedPeriods: vesting.Periods{},
isPeriodicVestingAccount: false,
},
errArgs{
expectPass: false,
contains: "claim amount rounds to zero",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
userAddr := suite.addrs[0]
authBulder := app.NewAuthGenesisBuilder().
WithSimpleAccount(userAddr, cs(tc.args.initialCollateral)).
WithSimpleModuleAccount(kavadist.ModuleName, cs(c("ukava", 1000000000000)))
incentBuilder := NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime).
WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond).
WithMultipliers(tc.args.multipliers)
suite.SetupWithGenState(authBulder, incentBuilder, NewHardGenStateMulti(suite.genesisTime))
// setup cdp state
err := suite.cdpKeeper.AddCdp(suite.ctx, userAddr, tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
suite.Require().NoError(err)
claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, userAddr)
suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype)
suite.Require().True(found)
err = suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
suite.Require().NoError(err)
err = suite.keeper.ClaimUSDXMintingReward(suite.ctx, userAddr, tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
acc := suite.getAccount(userAddr)
suite.Require().Equal(tc.args.expectedBalance, acc.GetCoins())
if tc.args.isPeriodicVestingAccount {
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
}
claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, userAddr)
suite.Require().True(found)
suite.Require().Equal(c("ukava", 0), claim.Reward)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *PayoutTestSuite) TestPayoutUSDXMintingClaimVVesting() {
type args struct {
ctype string
rewardsPerSecond sdk.Coin
initialCollateral sdk.Coin
initialPrincipal sdk.Coin
multipliers types.Multipliers
multiplier types.MultiplierName
timeElapsed int
expectedBalance sdk.Coins
expectedPeriods vesting.Periods
}
type errArgs struct {
expectPass bool
contains string
}
type test struct {
name string
args args
errArgs errArgs
}
testCases := []test{
{
"valid 1 day",
args{
ctype: "bnb-a",
rewardsPerSecond: c("ukava", 122354),
initialCollateral: c("bnb", 1e12),
initialPrincipal: c("usdx", 1e10),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedBalance: cs(c("ukava", 10571385600)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("ukava", 10571385600))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"invalid zero rewards",
args{
ctype: "bnb-a",
rewardsPerSecond: c("ukava", 0),
initialCollateral: c("bnb", 1e12),
initialPrincipal: c("usdx", 1e10),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedBalance: cs(),
expectedPeriods: vesting.Periods{},
},
errArgs{
expectPass: false,
contains: "claim amount rounds to zero",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
bacc := auth.NewBaseAccount(suite.addrs[2], cs(tc.args.initialCollateral, c("ukava", 400)), nil, 0, 0)
bva, err := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), suite.genesisTime.Unix()+16)
suite.Require().NoError(err)
periods := vesting.Periods{
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
}
vva := validatorvesting.NewValidatorVestingAccountRaw(bva, suite.genesisTime.Unix(), periods, sdk.ConsAddress{}, nil, 90)
authBulder := app.NewAuthGenesisBuilder().
WithAccounts(vva).
WithSimpleModuleAccount(kavadist.ModuleName, cs(c("ukava", 1e18))).
WithSimpleAccount(suite.addrs[0], cs()) // the recipient address needs to be a instantiated account // TODO change?
incentBuilder := NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime).
WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond).
WithMultipliers(tc.args.multipliers)
suite.SetupWithGenState(authBulder, incentBuilder, NewHardGenStateMulti(suite.genesisTime))
// setup cdp state
cdpKeeper := suite.app.GetCDPKeeper()
err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[2], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
suite.Require().NoError(err)
claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[2])
suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
// accumulate some usdx rewards
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype)
suite.Require().True(found)
err = suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
suite.Require().NoError(err)
err = suite.keeper.ClaimUSDXMintingRewardVVesting(suite.ctx, suite.addrs[2], suite.addrs[0], tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[0])
suite.Require().Equal(tc.args.expectedBalance, acc.GetCoins())
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[2])
suite.Require().True(found)
suite.Require().Equal(c("ukava", 0), claim.Reward)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *PayoutTestSuite) TestPayoutHardLiquidityProviderClaim() {
type args struct {
deposit sdk.Coins
borrow sdk.Coins
rewardsPerSecond sdk.Coins
multipliers types.Multipliers
multiplier types.MultiplierName
timeElapsed int64
expectedRewards sdk.Coins
expectedPeriods vesting.Periods
isPeriodicVestingAccount bool
}
type errArgs struct {
expectPass bool
contains string
}
type test struct {
name string
args args
errArgs errArgs
}
testCases := []test{
{
"single reward denom: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771200)), // 10571385600 (deposit reward) + 10571385600 (borrow reward)
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"single reward denom: valid 10 days",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 864000,
expectedRewards: cs(c("hard", 211427712000)), // 105713856000 (deposit reward) + 105713856000 (borrow reward)
expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712000))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771200), c("ukava", 21142771200)), // 10571385600 (deposit reward) + 10571385600 (borrow reward)
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200), c("ukava", 21142771200))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms: valid 10 days",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 864000,
expectedRewards: cs(c("hard", 211427712000), c("ukava", 211427712000)), // 105713856000 (deposit reward) + 105713856000 (borrow reward)
expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712000), c("ukava", 211427712000))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms with different rewards per second: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 222222)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771200), c("ukava", 38399961600)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200), c("ukava", 38399961600))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
userAddr := suite.addrs[3]
authBulder := app.NewAuthGenesisBuilder().
WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15))).
WithSimpleModuleAccount(kavadist.ModuleName, cs(c("hard", 1000000000000000000), c("ukava", 1000000000000000000)))
incentBuilder := NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime).
WithMultipliers(tc.args.multipliers)
for _, c := range tc.args.deposit {
incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(c.Denom, tc.args.rewardsPerSecond)
}
for _, c := range tc.args.borrow {
incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(c.Denom, tc.args.rewardsPerSecond)
}
suite.SetupWithGenState(authBulder, incentBuilder, NewHardGenStateMulti(suite.genesisTime))
// User deposits and borrows
err := suite.hardKeeper.Deposit(suite.ctx, userAddr, tc.args.deposit)
suite.Require().NoError(err)
err = suite.hardKeeper.Borrow(suite.ctx, userAddr, tc.args.borrow)
suite.Require().NoError(err)
// Check that Hard hooks initialized a HardLiquidityProviderClaim that has 0 rewards
claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, userAddr)
suite.Require().True(found)
for _, coin := range tc.args.deposit {
suite.Require().Equal(sdk.ZeroInt(), claim.Reward.AmountOf(coin.Denom))
}
// Set up future runtime context
runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.timeElapsed), 0)
runCtx := suite.ctx.WithBlockTime(runAtTime)
// Accumulate supply rewards for each deposit denom
for _, coin := range tc.args.deposit {
rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, coin.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
}
// Accumulate borrow rewards for each deposit denom
for _, coin := range tc.args.borrow {
rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, coin.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
}
// Sync hard supply rewards
deposit, found := suite.hardKeeper.GetDeposit(suite.ctx, userAddr)
suite.Require().True(found)
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
// Sync hard borrow rewards
borrow, found := suite.hardKeeper.GetBorrow(suite.ctx, userAddr)
suite.Require().True(found)
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
// Fetch pre-claim balances
ak := suite.app.GetAccountKeeper()
preClaimAcc := ak.GetAccount(runCtx, userAddr)
err = suite.keeper.ClaimHardReward(runCtx, userAddr, tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
// Check that user's balance has increased by expected reward amount
postClaimAcc := ak.GetAccount(suite.ctx, userAddr)
suite.Require().Equal(preClaimAcc.GetCoins().Add(tc.args.expectedRewards...), postClaimAcc.GetCoins())
if tc.args.isPeriodicVestingAccount {
vacc, ok := postClaimAcc.(*vesting.PeriodicVestingAccount)
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
}
// Check that each claim reward coin's amount has been reset to 0
claim, found := suite.keeper.GetHardLiquidityProviderClaim(runCtx, userAddr)
suite.Require().True(found)
for _, claimRewardCoin := range claim.Reward {
suite.Require().Equal(c(claimRewardCoin.Denom, 0), claimRewardCoin)
}
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *PayoutTestSuite) TestPayoutHardLiquidityProviderClaimVVesting() {
type args struct {
deposit sdk.Coins
borrow sdk.Coins
rewardsPerSecond sdk.Coins
multipliers types.Multipliers
multiplier types.MultiplierName
timeElapsed int64
expectedRewards sdk.Coins
expectedPeriods vesting.Periods
}
type errArgs struct {
expectPass bool
contains string
}
type test struct {
name string
args args
errArgs errArgs
}
testCases := []test{
{
"single reward denom: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771202)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771202))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"single reward denom: valid 10 days",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 864000,
expectedRewards: cs(c("hard", 211427712008)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712008))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771202), c("ukava", 21142771202)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771202), c("ukava", 21142771202))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms: valid 10 days",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 864000,
expectedRewards: cs(c("hard", 211427712008), c("ukava", 211427712008)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712008), c("ukava", 211427712008))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"multiple reward denoms with different rewards per second: valid 1 day",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 122354), c("ukava", 222222)),
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
multiplier: types.MultiplierName("large"),
timeElapsed: 86400,
expectedRewards: cs(c("hard", 21142771202), c("ukava", 38399961603)),
expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771202), c("ukava", 38399961603))}},
},
errArgs{
expectPass: true,
contains: "",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
userAddr := suite.addrs[3]
bacc := auth.NewBaseAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), nil, 0, 0)
bva, err := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), suite.genesisTime.Unix()+16)
suite.Require().NoError(err)
periods := vesting.Periods{
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
}
vva := validatorvesting.NewValidatorVestingAccountRaw(bva, suite.genesisTime.Unix(), periods, sdk.ConsAddress{}, nil, 90)
authBulder := app.NewAuthGenesisBuilder().
WithAccounts(vva).
WithSimpleAccount(suite.addrs[2], cs()).
WithSimpleModuleAccount(kavadist.ModuleName, cs(c("hard", 1000000000000000000), c("ukava", 1000000000000000000)))
incentBuilder := NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime).
WithMultipliers(tc.args.multipliers)
for _, c := range tc.args.deposit {
incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(c.Denom, tc.args.rewardsPerSecond)
}
for _, c := range tc.args.borrow {
incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(c.Denom, tc.args.rewardsPerSecond)
}
suite.SetupWithGenState(authBulder, incentBuilder, NewHardGenStateMulti(suite.genesisTime))
ak := suite.app.GetAccountKeeper()
hardKeeper := suite.app.GetHardKeeper()
// User deposits and borrows
err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.deposit)
suite.Require().NoError(err)
err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.borrow)
suite.Require().NoError(err)
// Check that Hard hooks initialized a HardLiquidityProviderClaim that has 0 rewards
claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, userAddr)
suite.Require().True(found)
for _, coin := range tc.args.deposit {
suite.Require().Equal(sdk.ZeroInt(), claim.Reward.AmountOf(coin.Denom))
}
// Set up future runtime context
runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.timeElapsed), 0)
runCtx := suite.ctx.WithBlockTime(runAtTime)
// Run Hard begin blocker
hard.BeginBlocker(runCtx, suite.hardKeeper)
// Accumulate supply rewards for each deposit denom
for _, coin := range tc.args.deposit {
rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, coin.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
}
// Accumulate borrow rewards for each deposit denom
for _, coin := range tc.args.borrow {
rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, coin.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
}
// Sync hard supply rewards
deposit, found := suite.hardKeeper.GetDeposit(suite.ctx, userAddr)
suite.Require().True(found)
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
// Sync hard borrow rewards
borrow, found := suite.hardKeeper.GetBorrow(suite.ctx, userAddr)
suite.Require().True(found)
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
// Fetch pre-claim balances
preClaimAcc := ak.GetAccount(runCtx, suite.addrs[2])
err = suite.keeper.ClaimHardRewardVVesting(runCtx, userAddr, suite.addrs[2], tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
// Check that user's balance has increased by expected reward amount
postClaimAcc := ak.GetAccount(suite.ctx, suite.addrs[2])
suite.Require().Equal(preClaimAcc.GetCoins().Add(tc.args.expectedRewards...), postClaimAcc.GetCoins())
vacc, ok := postClaimAcc.(*vesting.PeriodicVestingAccount)
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
// Check that each claim reward coin's amount has been reset to 0
claim, found := suite.keeper.GetHardLiquidityProviderClaim(runCtx, suite.addrs[3])
suite.Require().True(found)
for _, claimRewardCoin := range claim.Reward {
suite.Require().Equal(c(claimRewardCoin.Denom, 0), claimRewardCoin)
}
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *PayoutTestSuite) TestSendCoinsToPeriodicVestingAccount() { func (suite *PayoutTestSuite) TestSendCoinsToPeriodicVestingAccount() {
type accountArgs struct { type accountArgs struct {
periods vesting.Periods periods vesting.Periods

View File

@ -15,6 +15,7 @@ import (
"github.com/kava-labs/kava/x/hard" "github.com/kava-labs/kava/x/hard"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper" hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
@ -53,7 +54,7 @@ func (suite *BorrowRewardsTestSuite) SetupApp() {
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})
} }
func (suite *BorrowRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder IncentiveGenesisBuilder, hardBuilder HardGenesisBuilder) { func (suite *BorrowRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder, hardBuilder testutil.HardGenesisBuilder) {
suite.SetupApp() suite.SetupApp()
suite.app.InitializeFromGenesisStatesWithTime( suite.app.InitializeFromGenesisStatesWithTime(
@ -162,7 +163,7 @@ func (suite *BorrowRewardsTestSuite) TestAccumulateHardBorrowRewards() {
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleBorrowRewardPeriod(tc.args.borrow.Denom, tc.args.rewardsPerSecond) WithSimpleBorrowRewardPeriod(tc.args.borrow.Denom, tc.args.rewardsPerSecond)
@ -317,7 +318,7 @@ func (suite *BorrowRewardsTestSuite) TestInitializeHardBorrowRewards() {
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime)
for moneyMarketDenom, rewardsPerSecond := range tc.args.moneyMarketRewardDenoms { for moneyMarketDenom, rewardsPerSecond := range tc.args.moneyMarketRewardDenoms {
incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(moneyMarketDenom, rewardsPerSecond) incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(moneyMarketDenom, rewardsPerSecond)
} }
@ -506,7 +507,7 @@ func (suite *BorrowRewardsTestSuite) TestSynchronizeHardBorrowReward() {
WithSimpleAccount(suite.addrs[2], cs(c("ukava", 1e9))). WithSimpleAccount(suite.addrs[2], cs(c("ukava", 1e9))).
WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15))) WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)))
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime)
if tc.args.rewardsPerSecond != nil { if tc.args.rewardsPerSecond != nil {
incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(tc.args.incentiveBorrowRewardDenom, tc.args.rewardsPerSecond) incentBuilder = incentBuilder.WithSimpleBorrowRewardPeriod(tc.args.incentiveBorrowRewardDenom, tc.args.rewardsPerSecond)
} }
@ -833,7 +834,7 @@ func (suite *BorrowRewardsTestSuite) TestUpdateHardBorrowIndexDenoms() {
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleBorrowRewardPeriod("bnb", tc.args.rewardsPerSecond). WithSimpleBorrowRewardPeriod("bnb", tc.args.rewardsPerSecond).
WithSimpleBorrowRewardPeriod("ukava", tc.args.rewardsPerSecond). WithSimpleBorrowRewardPeriod("ukava", tc.args.rewardsPerSecond).
@ -935,7 +936,7 @@ func (suite *BorrowRewardsTestSuite) TestSimulateHardBorrowRewardSynchronization
userAddr := suite.addrs[3] userAddr := suite.addrs[3]
authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15))) authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)))
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleBorrowRewardPeriod(tc.args.borrow.Denom, tc.args.rewardsPerSecond) WithSimpleBorrowRewardPeriod(tc.args.borrow.Denom, tc.args.rewardsPerSecond)

View File

@ -172,10 +172,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewReward
suite.Equal(newGlobalIndexes, syncedClaim.RewardIndexes) suite.Equal(newGlobalIndexes, syncedClaim.RewardIndexes)
suite.Equal( suite.Equal(
cs( cs(c("hard", 100), c("swp", 200)).Add(claim.Reward...),
c(types.HardLiquidityRewardDenom, 100),
c("swp", 200),
).Add(claim.Reward...),
syncedClaim.Reward, syncedClaim.Reward,
) )
} }
@ -237,10 +234,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFac
syncedClaim, _ := suite.keeper.GetDelegatorClaim(suite.ctx, claim.Owner) syncedClaim, _ := suite.keeper.GetDelegatorClaim(suite.ctx, claim.Owner)
suite.Equal( suite.Equal(
cs( cs(c("hard", 100), c("swp", 200)).Add(claim.Reward...),
c(types.HardLiquidityRewardDenom, 100),
c("swp", 200),
).Add(claim.Reward...),
syncedClaim.Reward, syncedClaim.Reward,
) )
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
@ -53,7 +54,7 @@ func (suite *DelegatorRewardsTestSuite) SetupApp() {
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})
} }
func (suite *DelegatorRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder IncentiveGenesisBuilder) { func (suite *DelegatorRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder) {
suite.SetupApp() suite.SetupApp()
suite.app.InitializeFromGenesisStatesWithTime( suite.app.InitializeFromGenesisStatesWithTime(
@ -128,7 +129,7 @@ func (suite *DelegatorRewardsTestSuite) TestAccumulateDelegatorRewards() {
WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))). WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))).
WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9))) WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9)))
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond) WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond)
@ -226,7 +227,7 @@ func (suite *DelegatorRewardsTestSuite) TestSynchronizeDelegatorReward() {
WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))). WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))).
WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9))) WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9)))
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond) WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond)
@ -351,7 +352,7 @@ func (suite *DelegatorRewardsTestSuite) TestSimulateDelegatorRewardSynchronizati
WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))). WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))).
WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9))) WithSimpleAccount(sdk.AccAddress(suite.validatorAddrs[0]), cs(c("ukava", 1e9)))
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond) WithSimpleDelegatorRewardPeriod(tc.args.delegation.Denom, tc.args.rewardsPerSecond)
@ -458,7 +459,7 @@ func (suite *DelegatorRewardsTestSuite) TestUnbondingValidatorSyncsClaim() {
rewardsPerSecond := cs(c("hard", 122354)) rewardsPerSecond := cs(c("hard", 122354))
bondDenom := "ukava" bondDenom := "ukava"
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond) WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond)
@ -552,7 +553,7 @@ func (suite *DelegatorRewardsTestSuite) TestBondingValidatorSyncsClaim() {
rewardsPerSecond := cs(c("hard", 122354)) rewardsPerSecond := cs(c("hard", 122354))
bondDenom := "ukava" bondDenom := "ukava"
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond) WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond)
@ -644,7 +645,7 @@ func (suite *DelegatorRewardsTestSuite) TestSlashingValidatorSyncsClaim() {
rewardsPerSecond := cs(c("hard", 122354)) rewardsPerSecond := cs(c("hard", 122354))
bondDenom := "ukava" bondDenom := "ukava"
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond) WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond)
@ -725,7 +726,7 @@ func (suite *DelegatorRewardsTestSuite) TestRedelegationSyncsClaim() {
rewardsPerSecond := cs(c("hard", 122354)) rewardsPerSecond := cs(c("hard", 122354))
bondDenom := "ukava" bondDenom := "ukava"
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond) WithSimpleDelegatorRewardPeriod(bondDenom, rewardsPerSecond)

View File

@ -15,6 +15,7 @@ import (
"github.com/kava-labs/kava/x/hard" "github.com/kava-labs/kava/x/hard"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper" hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
@ -53,7 +54,7 @@ func (suite *SupplyRewardsTestSuite) SetupApp() {
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})
} }
func (suite *SupplyRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder IncentiveGenesisBuilder, hardBuilder HardGenesisBuilder) { func (suite *SupplyRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder, hardBuilder testutil.HardGenesisBuilder) {
suite.SetupApp() suite.SetupApp()
suite.app.InitializeFromGenesisStatesWithTime( suite.app.InitializeFromGenesisStatesWithTime(
@ -163,7 +164,7 @@ func (suite *SupplyRewardsTestSuite) TestAccumulateHardSupplyRewards() {
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
// suite.SetupWithGenState(authBuilder) // suite.SetupWithGenState(authBuilder)
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime) WithGenesisTime(suite.genesisTime)
if tc.args.rewardsPerSecond != nil { if tc.args.rewardsPerSecond != nil {
incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(tc.args.deposit.Denom, tc.args.rewardsPerSecond) incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(tc.args.deposit.Denom, tc.args.rewardsPerSecond)
@ -317,7 +318,7 @@ func (suite *SupplyRewardsTestSuite) TestInitializeHardSupplyRewards() {
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime)
for moneyMarketDenom, rewardsPerSecond := range tc.args.moneyMarketRewardDenoms { for moneyMarketDenom, rewardsPerSecond := range tc.args.moneyMarketRewardDenoms {
incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(moneyMarketDenom, rewardsPerSecond) incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(moneyMarketDenom, rewardsPerSecond)
} }
@ -506,7 +507,7 @@ func (suite *SupplyRewardsTestSuite) TestSynchronizeHardSupplyReward() {
WithSimpleAccount(suite.addrs[2], cs(c("ukava", 1e9))). WithSimpleAccount(suite.addrs[2], cs(c("ukava", 1e9))).
WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15))) WithSimpleAccount(userAddr, cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)))
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime) WithGenesisTime(suite.genesisTime)
if tc.args.rewardsPerSecond != nil { if tc.args.rewardsPerSecond != nil {
incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(tc.args.incentiveSupplyRewardDenom, tc.args.rewardsPerSecond) incentBuilder = incentBuilder.WithSimpleSupplyRewardPeriod(tc.args.incentiveSupplyRewardDenom, tc.args.rewardsPerSecond)
@ -809,7 +810,7 @@ func (suite *SupplyRewardsTestSuite) TestUpdateHardSupplyIndexDenoms() {
userAddr, userAddr,
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleSupplyRewardPeriod("bnb", tc.args.rewardsPerSecond). WithSimpleSupplyRewardPeriod("bnb", tc.args.rewardsPerSecond).
WithSimpleSupplyRewardPeriod("ukava", tc.args.rewardsPerSecond). WithSimpleSupplyRewardPeriod("ukava", tc.args.rewardsPerSecond).
@ -893,7 +894,7 @@ func (suite *SupplyRewardsTestSuite) TestSimulateHardSupplyRewardSynchronization
userAddr, userAddr,
cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)), cs(c("bnb", 1e15), c("ukava", 1e15), c("btcb", 1e15), c("xrp", 1e15), c("zzz", 1e15)),
) )
incentBuilder := NewIncentiveGenesisBuilder(). incentBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.genesisTime). WithGenesisTime(suite.genesisTime).
WithSimpleSupplyRewardPeriod(tc.args.deposit.Denom, tc.args.rewardsPerSecond) WithSimpleSupplyRewardPeriod(tc.args.deposit.Denom, tc.args.rewardsPerSecond)

View File

@ -110,7 +110,7 @@ func (k Keeper) GetSynchronizedSwapClaim(ctx sdk.Context, owner sdk.AccAddress)
for _, indexes := range claim.RewardIndexes { for _, indexes := range claim.RewardIndexes {
poolID := indexes.CollateralType poolID := indexes.CollateralType
shares, found := k.swapKeeper.GetDepositorSharesInPool(ctx, owner, poolID) shares, found := k.swapKeeper.GetDepositorSharesAmount(ctx, owner, poolID)
if !found { if !found {
shares = sdk.ZeroInt() shares = sdk.ZeroInt()
} }

View File

@ -258,7 +258,7 @@ type fakeSwapKeeper struct {
func (k fakeSwapKeeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) { func (k fakeSwapKeeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) {
return k.poolShares, true return k.poolShares, true
} }
func (k fakeSwapKeeper) GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) { func (k fakeSwapKeeper) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
// This is just to implement the swap keeper interface. // This is just to implement the swap keeper interface.
return sdk.Int{}, false return sdk.Int{}, false
} }

View File

@ -12,6 +12,7 @@ import (
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper" cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/testutil"
) )
// Test suite used for all keeper tests // Test suite used for all keeper tests
@ -47,7 +48,7 @@ func (suite *USDXRewardsTestSuite) SetupApp() {
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})
} }
func (suite *USDXRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder IncentiveGenesisBuilder) { func (suite *USDXRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder) {
suite.SetupApp() suite.SetupApp()
suite.app.InitializeFromGenesisStatesWithTime( suite.app.InitializeFromGenesisStatesWithTime(
@ -105,7 +106,7 @@ func (suite *USDXRewardsTestSuite) TestAccumulateUSDXMintingRewards() {
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond)
suite.SetupWithGenState(app.NewAuthGenesisBuilder(), incentBuilder) suite.SetupWithGenState(app.NewAuthGenesisBuilder(), incentBuilder)
@ -169,7 +170,7 @@ func (suite *USDXRewardsTestSuite) TestSynchronizeUSDXMintingReward() {
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(suite.addrs[0], cs(tc.args.initialCollateral)) authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(suite.addrs[0], cs(tc.args.initialCollateral))
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond)
suite.SetupWithGenState(authBuilder, incentBuilder) suite.SetupWithGenState(authBuilder, incentBuilder)
@ -256,7 +257,7 @@ func (suite *USDXRewardsTestSuite) TestSimulateUSDXMintingRewardSynchronization(
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(suite.addrs[0], cs(tc.args.initialCollateral)) authBuilder := app.NewAuthGenesisBuilder().WithSimpleAccount(suite.addrs[0], cs(tc.args.initialCollateral))
incentBuilder := NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond) incentBuilder := testutil.NewIncentiveGenesisBuilder().WithGenesisTime(suite.genesisTime).WithSimpleUSDXRewardPeriod(tc.args.ctype, tc.args.rewardsPerSecond)
suite.SetupWithGenState(authBuilder, incentBuilder) suite.SetupWithGenState(authBuilder, incentBuilder)

View File

@ -0,0 +1,201 @@
package testutil
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/hard"
hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types"
)
const (
oneYear time.Duration = time.Hour * 24 * 365
)
// IncentiveGenesisBuilder is a tool for creating an incentive genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type IncentiveGenesisBuilder struct {
types.GenesisState
genesisTime time.Time
}
func NewIncentiveGenesisBuilder() IncentiveGenesisBuilder {
return IncentiveGenesisBuilder{
GenesisState: types.DefaultGenesisState(),
genesisTime: time.Time{},
}
}
func (builder IncentiveGenesisBuilder) Build() types.GenesisState {
return builder.GenesisState
}
func (builder IncentiveGenesisBuilder) BuildMarshalled() app.GenesisState {
return app.GenesisState{
types.ModuleName: types.ModuleCdc.MustMarshalJSON(builder.Build()),
}
}
func (builder IncentiveGenesisBuilder) WithGenesisTime(time time.Time) IncentiveGenesisBuilder {
builder.genesisTime = time
builder.Params.ClaimEnd = time.Add(5 * oneYear)
return builder
}
func (builder IncentiveGenesisBuilder) WithInitializedBorrowRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.HardBorrowRewardPeriods = append(builder.Params.HardBorrowRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.HardBorrowAccumulationTimes = append(builder.HardBorrowAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleBorrowRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedBorrowRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.HardSupplyRewardPeriods = append(builder.Params.HardSupplyRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.HardSupplyAccumulationTimes = append(builder.HardSupplyAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleSupplyRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedSupplyRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedDelegatorRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.DelegatorRewardPeriods = append(builder.Params.DelegatorRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.DelegatorAccumulationTimes = append(builder.DelegatorAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleDelegatorRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedDelegatorRewardPeriod(types.NewMultiRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedSwapRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.SwapRewardPeriods = append(builder.Params.SwapRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.SwapAccumulationTimes = append(builder.SwapAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleSwapRewardPeriod(poolID string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedSwapRewardPeriod(types.NewMultiRewardPeriod(
true,
poolID,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithInitializedUSDXRewardPeriod(period types.RewardPeriod) IncentiveGenesisBuilder {
builder.Params.USDXMintingRewardPeriods = append(builder.Params.USDXMintingRewardPeriods, period)
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
builder.USDXAccumulationTimes = append(builder.USDXAccumulationTimes, accumulationTimeForPeriod)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleUSDXRewardPeriod(ctype string, rewardsPerSecond sdk.Coin) IncentiveGenesisBuilder {
return builder.WithInitializedUSDXRewardPeriod(types.NewRewardPeriod(
true,
ctype,
builder.genesisTime,
builder.genesisTime.Add(4*oneYear),
rewardsPerSecond,
))
}
func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.Multipliers) IncentiveGenesisBuilder {
builder.Params.ClaimMultipliers = multipliers
return builder
}
// HardGenesisBuilder is a tool for creating a hard genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type HardGenesisBuilder struct {
hardtypes.GenesisState
genesisTime time.Time
}
func NewHardGenesisBuilder() HardGenesisBuilder {
return HardGenesisBuilder{
GenesisState: hardtypes.DefaultGenesisState(),
}
}
func (builder HardGenesisBuilder) Build() hardtypes.GenesisState {
return builder.GenesisState
}
func (builder HardGenesisBuilder) BuildMarshalled() app.GenesisState {
return app.GenesisState{
hardtypes.ModuleName: hardtypes.ModuleCdc.MustMarshalJSON(builder.Build()),
}
}
func (builder HardGenesisBuilder) WithGenesisTime(genTime time.Time) HardGenesisBuilder {
builder.genesisTime = genTime
return builder
}
func (builder HardGenesisBuilder) WithInitializedMoneyMarket(market hard.MoneyMarket) HardGenesisBuilder {
builder.Params.MoneyMarkets = append(builder.Params.MoneyMarkets, market)
builder.PreviousAccumulationTimes = append(
builder.PreviousAccumulationTimes,
hardtypes.NewGenesisAccumulationTime(market.Denom, builder.genesisTime, sdk.OneDec(), sdk.OneDec()),
)
return builder
}
func (builder HardGenesisBuilder) WithMinBorrow(minUSDValue sdk.Dec) HardGenesisBuilder {
builder.Params.MinimumBorrowUSDValue = minUSDValue
return builder
}
func NewStandardMoneyMarket(denom string) hardtypes.MoneyMarket {
return hardtypes.NewMoneyMarket(
denom,
hard.NewBorrowLimit(
false,
sdk.NewDec(1e15),
sdk.MustNewDecFromStr("0.6"),
),
denom+":usd",
sdk.NewInt(1e6),
hard.NewInterestRateModel(
sdk.MustNewDecFromStr("0.05"),
sdk.MustNewDecFromStr("2"),
sdk.MustNewDecFromStr("0.8"),
sdk.MustNewDecFromStr("10"),
),
sdk.MustNewDecFromStr("0.05"),
sdk.ZeroDec(),
)
}

View File

@ -0,0 +1,167 @@
package testutil
import (
"errors"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/staking"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/hard"
"github.com/kava-labs/kava/x/incentive"
"github.com/kava-labs/kava/x/swap"
)
type IntegrationTester struct {
suite.Suite
App app.TestApp
Ctx sdk.Context
}
func (suite *IntegrationTester) NextBlockAt(blockTime time.Time) {
if !suite.Ctx.BlockTime().Before(blockTime) {
panic(fmt.Sprintf("new block time %s must be after current %s", blockTime, suite.Ctx.BlockTime()))
}
blockHeight := suite.Ctx.BlockHeight() + 1
_ = suite.App.EndBlocker(suite.Ctx, abci.RequestEndBlock{})
suite.Ctx = suite.Ctx.WithBlockTime(blockTime).WithBlockHeight(blockHeight)
_ = suite.App.BeginBlocker(suite.Ctx, abci.RequestBeginBlock{}) // height and time in RequestBeginBlock are ignored by module begin blockers
}
func (suite *IntegrationTester) NextBlockAfter(blockDuration time.Duration) {
suite.NextBlockAt(suite.Ctx.BlockTime().Add(blockDuration))
}
func (suite *IntegrationTester) DeliverIncentiveMsg(msg sdk.Msg) error {
handler := incentive.NewHandler(suite.App.GetIncentiveKeeper())
_, err := handler(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverMsgCreateValidator(address sdk.ValAddress, selfDelegation sdk.Coin) error {
msg := staking.NewMsgCreateValidator(
address,
ed25519.GenPrivKey().PubKey(),
selfDelegation,
staking.Description{},
staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
sdk.NewInt(1_000_000),
)
handler := staking.NewHandler(suite.App.GetStakingKeeper())
_, err := handler(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverMsgDelegate(delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error {
msg := staking.NewMsgDelegate(
delegator,
validator,
amount,
)
handleStakingMsg := staking.NewHandler(suite.App.GetStakingKeeper())
_, err := handleStakingMsg(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverSwapMsgDeposit(depositor sdk.AccAddress, tokenA, tokenB sdk.Coin, slippage sdk.Dec) error {
msg := swap.NewMsgDeposit(
depositor,
tokenA,
tokenB,
slippage,
suite.Ctx.BlockTime().Add(time.Hour).Unix(), // ensure msg will not fail due to short deadline
)
_, err := swap.NewHandler(suite.App.GetSwapKeeper())(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverHardMsgDeposit(depositor sdk.AccAddress, deposit sdk.Coins) error {
msg := hard.NewMsgDeposit(depositor, deposit)
_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverHardMsgBorrow(depositor sdk.AccAddress, borrow sdk.Coins) error {
msg := hard.NewMsgBorrow(depositor, borrow)
_, err := hard.NewHandler(suite.App.GetHardKeeper())(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) DeliverMsgCreateCDP(owner sdk.AccAddress, collateral, principal sdk.Coin, collateralType string) error {
msg := cdp.NewMsgCreateCDP(owner, collateral, principal, collateralType)
_, err := cdp.NewHandler(suite.App.GetCDPKeeper())(suite.Ctx, msg)
return err
}
func (suite *IntegrationTester) GetAccount(addr sdk.AccAddress) authexported.Account {
ak := suite.App.GetAccountKeeper()
return ak.GetAccount(suite.Ctx, addr)
}
func (suite *IntegrationTester) GetModuleAccount(name string) supplyexported.ModuleAccountI {
sk := suite.App.GetSupplyKeeper()
return sk.GetModuleAccount(suite.Ctx, name)
}
func (suite *IntegrationTester) GetBalance(address sdk.AccAddress) sdk.Coins {
acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, address)
if acc != nil {
return acc.GetCoins()
} else {
return nil
}
}
func (suite *IntegrationTester) ErrorIs(err, target error) bool {
return suite.Truef(errors.Is(err, target), "err didn't match: %s, it was: %s", target, err)
}
func (suite *IntegrationTester) BalanceEquals(address sdk.AccAddress, expected sdk.Coins) {
acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, address)
suite.Require().NotNil(acc, "expected account to not be nil")
suite.Equalf(expected, acc.GetCoins(), "expected account balance to equal coins %s, but got %s", expected, acc.GetCoins())
}
func (suite *IntegrationTester) VestingPeriodsEqual(address sdk.AccAddress, expectedPeriods vesting.Periods) {
acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, address)
suite.Require().NotNil(acc, "expected vesting account not to be nil")
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
suite.Require().True(ok, "expected vesting account to be type PeriodicVestingAccount")
suite.Equal(expectedPeriods, vacc.VestingPeriods)
}
func (suite *IntegrationTester) SwapRewardEquals(owner sdk.AccAddress, expected sdk.Coins) {
claim, found := suite.App.GetIncentiveKeeper().GetSwapClaim(suite.Ctx, owner)
suite.Require().Truef(found, "expected swap claim to be found for %s", owner)
suite.Equalf(expected, claim.Reward, "expected swap claim reward to be %s, but got %s", expected, claim.Reward)
}
func (suite *IntegrationTester) DelegatorRewardEquals(owner sdk.AccAddress, expected sdk.Coins) {
claim, found := suite.App.GetIncentiveKeeper().GetDelegatorClaim(suite.Ctx, owner)
suite.Require().Truef(found, "expected delegator claim to be found for %s", owner)
suite.Equalf(expected, claim.Reward, "expected delegator claim reward to be %s, but got %s", expected, claim.Reward)
}
func (suite *IntegrationTester) HardRewardEquals(owner sdk.AccAddress, expected sdk.Coins) {
claim, found := suite.App.GetIncentiveKeeper().GetHardLiquidityProviderClaim(suite.Ctx, owner)
suite.Require().Truef(found, "expected delegator claim to be found for %s", owner)
suite.Equalf(expected, claim.Reward, "expected delegator claim reward to be %s, but got %s", expected, claim.Reward)
}
func (suite *IntegrationTester) USDXRewardEquals(owner sdk.AccAddress, expected sdk.Coin) {
claim, found := suite.App.GetIncentiveKeeper().GetUSDXMintingClaim(suite.Ctx, owner)
suite.Require().Truef(found, "expected delegator claim to be found for %s", owner)
suite.Equalf(expected, claim.Reward, "expected delegator claim reward to be %s, but got %s", expected, claim.Reward)
}

View File

@ -1,14 +0,0 @@
package types
import (
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
)
// GetTotalVestingPeriodLength returns the summed length of all vesting periods
func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
length := int64(0)
for _, period := range periods {
length += period.Length
}
return length
}

View File

@ -1,53 +0,0 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types"
)
type accountTest struct {
periods vesting.Periods
expectedVal int64
}
type AccountTestSuite struct {
suite.Suite
tests []accountTest
}
func (suite *AccountTestSuite) SetupTest() {
tests := []accountTest{
{
periods: vesting.Periods{
vesting.Period{
Length: int64(100),
Amount: sdk.Coins{},
},
vesting.Period{
Length: int64(200),
Amount: sdk.Coins{},
},
},
expectedVal: int64(300),
},
}
suite.tests = tests
}
func (suite *AccountTestSuite) TestGetTotalPeriodLength() {
for _, t := range suite.tests {
length := types.GetTotalVestingPeriodLength(t.periods)
suite.Equal(t.expectedVal, length)
}
}
func TestAccountTestSuite(t *testing.T) {
suite.Run(t, new(AccountTestSuite))
}

View File

@ -13,7 +13,8 @@ const (
HardLiquidityProviderClaimType = "hard_liquidity_provider" HardLiquidityProviderClaimType = "hard_liquidity_provider"
DelegatorClaimType = "delegator_claim" DelegatorClaimType = "delegator_claim"
SwapClaimType = "swap" SwapClaimType = "swap"
BondDenom = "ukava"
BondDenom = "ukava"
) )
// Claim is an interface for handling common claim actions // Claim is an interface for handling common claim actions

View File

@ -18,12 +18,16 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(USDXMintingClaim{}, "incentive/USDXMintingClaim", nil) cdc.RegisterConcrete(USDXMintingClaim{}, "incentive/USDXMintingClaim", nil)
cdc.RegisterConcrete(HardLiquidityProviderClaim{}, "incentive/HardLiquidityProviderClaim", nil) cdc.RegisterConcrete(HardLiquidityProviderClaim{}, "incentive/HardLiquidityProviderClaim", nil)
cdc.RegisterConcrete(DelegatorClaim{}, "incentive/DelegatorClaim", nil) cdc.RegisterConcrete(DelegatorClaim{}, "incentive/DelegatorClaim", nil)
cdc.RegisterConcrete(SwapClaim{}, "incentive/SwapClaim", nil)
// Register msgs // Register msgs
cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil) cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil)
cdc.RegisterConcrete(MsgClaimHardReward{}, "incentive/MsgClaimHardReward", nil) cdc.RegisterConcrete(MsgClaimHardReward{}, "incentive/MsgClaimHardReward", nil)
cdc.RegisterConcrete(MsgClaimDelegatorReward{}, "incentive/MsgClaimDelegatorReward", nil) cdc.RegisterConcrete(MsgClaimDelegatorReward{}, "incentive/MsgClaimDelegatorReward", nil)
cdc.RegisterConcrete(MsgClaimSwapReward{}, "incentive/MsgClaimSwapReward", nil)
cdc.RegisterConcrete(MsgClaimUSDXMintingRewardVVesting{}, "incentive/MsgClaimUSDXRewardVVesting", nil) cdc.RegisterConcrete(MsgClaimUSDXMintingRewardVVesting{}, "incentive/MsgClaimUSDXRewardVVesting", nil)
cdc.RegisterConcrete(MsgClaimHardRewardVVesting{}, "incentive/MsgClaimHardRewardVVesting", nil) cdc.RegisterConcrete(MsgClaimHardRewardVVesting{}, "incentive/MsgClaimHardRewardVVesting", nil)
cdc.RegisterConcrete(MsgClaimDelegatorRewardVVesting{}, "incentive/MsgClaimDelegatorRewardVVesting", nil) cdc.RegisterConcrete(MsgClaimDelegatorRewardVVesting{}, "incentive/MsgClaimDelegatorRewardVVesting", nil)
cdc.RegisterConcrete(MsgClaimSwapRewardVVesting{}, "incentive/MsgClaimSwapRewardVVesting", nil)
} }

View File

@ -20,4 +20,5 @@ var (
ErrInvalidClaimType = sdkerrors.Register(ModuleName, 11, "invalid claim type") ErrInvalidClaimType = sdkerrors.Register(ModuleName, 11, "invalid claim type")
ErrInvalidClaimOwner = sdkerrors.Register(ModuleName, 12, "invalid claim owner") 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")
) )

View File

@ -54,7 +54,7 @@ type HardKeeper interface {
// SwapKeeper defines the required methods needed by this modules keeper // SwapKeeper defines the required methods needed by this modules keeper
type SwapKeeper interface { type SwapKeeper interface {
GetPoolShares(ctx sdk.Context, poolID string) (shares sdk.Int, found bool) GetPoolShares(ctx sdk.Context, poolID string) (shares sdk.Int, found bool)
GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (shares sdk.Int, found bool) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (shares sdk.Int, found bool)
} }
// AccountKeeper defines the expected keeper interface for interacting with account // AccountKeeper defines the expected keeper interface for interacting with account

View File

@ -17,10 +17,6 @@ const (
QuerierRoute = ModuleName QuerierRoute = ModuleName
) )
// TODO: Refactor so that each incentive type has:
// 1. [Incentive]ClaimKeyPrefix
// 2. [Incentve]AccumulatorKeyPrefix { PreviousAccrualTime block.Time, IndexFactors types.IndexFactors }
// Key Prefixes // Key Prefixes
var ( var (
USDXMintingClaimKeyPrefix = []byte{0x01} // prefix for keys that store USDX minting claims USDXMintingClaimKeyPrefix = []byte{0x01} // prefix for keys that store USDX minting claims
@ -38,6 +34,5 @@ var (
SwapRewardIndexesKeyPrefix = []byte{0x13} // prefix for key that stores swap reward indexes SwapRewardIndexesKeyPrefix = []byte{0x13} // prefix for key that stores swap reward indexes
PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x14} // prefix for key that stores the previous time swap rewards accrued PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x14} // prefix for key that stores the previous time swap rewards accrued
USDXMintingRewardDenom = "ukava" USDXMintingRewardDenom = "ukava"
HardLiquidityRewardDenom = "hard"
) )

View File

@ -1,17 +1,21 @@
package types package types
import ( import (
"strings"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
) )
const MaxDenomsToClaim = 1000
// ensure Msg interface compliance at compile time // ensure Msg interface compliance at compile time
var _ sdk.Msg = &MsgClaimUSDXMintingReward{} var _ sdk.Msg = &MsgClaimUSDXMintingReward{}
var _ sdk.Msg = &MsgClaimUSDXMintingRewardVVesting{}
var _ sdk.Msg = &MsgClaimHardReward{} var _ sdk.Msg = &MsgClaimHardReward{}
var _ sdk.Msg = &MsgClaimHardRewardVVesting{} var _ sdk.Msg = &MsgClaimHardRewardVVesting{}
var _ sdk.Msg = &MsgClaimUSDXMintingRewardVVesting{} var _ sdk.Msg = &MsgClaimDelegatorReward{}
var _ sdk.Msg = &MsgClaimDelegatorRewardVVesting{}
var _ sdk.Msg = &MsgClaimSwapReward{}
var _ sdk.Msg = &MsgClaimSwapRewardVVesting{}
// MsgClaimUSDXMintingReward message type used to claim USDX minting rewards // MsgClaimUSDXMintingReward message type used to claim USDX minting rewards
type MsgClaimUSDXMintingReward struct { type MsgClaimUSDXMintingReward struct {
@ -38,7 +42,10 @@ func (msg MsgClaimUSDXMintingReward) 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
return err
}
return nil
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -84,7 +91,10 @@ func (msg MsgClaimUSDXMintingRewardVVesting) 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
return err
}
return nil
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -102,13 +112,15 @@ func (msg MsgClaimUSDXMintingRewardVVesting) GetSigners() []sdk.AccAddress {
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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
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) MsgClaimHardReward { func NewMsgClaimHardReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimHardReward {
return MsgClaimHardReward{ return MsgClaimHardReward{
Sender: sender, Sender: sender,
MultiplierName: multiplierName, MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
} }
} }
@ -125,7 +137,18 @@ 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -144,14 +167,16 @@ 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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
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) MsgClaimHardRewardVVesting { func NewMsgClaimHardRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimHardRewardVVesting {
return MsgClaimHardRewardVVesting{ return MsgClaimHardRewardVVesting{
Sender: sender, Sender: sender,
Receiver: receiver, Receiver: receiver,
MultiplierName: multiplierName, MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
} }
} }
@ -171,7 +196,18 @@ 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -189,13 +225,15 @@ func (msg MsgClaimHardRewardVVesting) GetSigners() []sdk.AccAddress {
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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
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) MsgClaimDelegatorReward { func NewMsgClaimDelegatorReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimDelegatorReward {
return MsgClaimDelegatorReward{ return MsgClaimDelegatorReward{
Sender: sender, Sender: sender,
MultiplierName: multiplierName, MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
} }
} }
@ -212,7 +250,18 @@ 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -231,14 +280,16 @@ 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"` MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
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) MsgClaimDelegatorRewardVVesting { func NewMsgClaimDelegatorRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimDelegatorRewardVVesting {
return MsgClaimDelegatorRewardVVesting{ return MsgClaimDelegatorRewardVVesting{
Sender: sender, Sender: sender,
Receiver: receiver, Receiver: receiver,
MultiplierName: multiplierName, MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
} }
} }
@ -258,7 +309,18 @@ 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")
} }
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid() if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.
@ -271,3 +333,116 @@ func (msg MsgClaimDelegatorRewardVVesting) GetSignBytes() []byte {
func (msg MsgClaimDelegatorRewardVVesting) GetSigners() []sdk.AccAddress { func (msg MsgClaimDelegatorRewardVVesting) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender} return []sdk.AccAddress{msg.Sender}
} }
// MsgClaimSwapReward message type used to claim delegator rewards
type MsgClaimSwapReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
}
// NewMsgClaimSwapReward returns a new MsgClaimSwapReward.
func NewMsgClaimSwapReward(sender sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimSwapReward {
return MsgClaimSwapReward{
Sender: sender,
MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
}
}
// Route return the message type used for routing the message.
func (msg MsgClaimSwapReward) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimSwapReward) Type() string {
return "claim_swap_reward"
}
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimSwapReward) ValidateBasic() error {
if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
}
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
}
// GetSignBytes gets the canonical byte representation of the Msg.
func (msg MsgClaimSwapReward) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// GetSigners returns the addresses of signers that must sign.
func (msg MsgClaimSwapReward) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}
// MsgClaimSwapRewardVVesting message type used to claim delegator rewards for validator vesting accounts
type MsgClaimSwapRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
DenomsToClaim []string `json:"denoms_to_claim" yaml:"denoms_to_claim"`
}
// MsgClaimSwapRewardVVesting returns a new MsgClaimSwapRewardVVesting.
func NewMsgClaimSwapRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string, denomsToClaim []string) MsgClaimSwapRewardVVesting {
return MsgClaimSwapRewardVVesting{
Sender: sender,
Receiver: receiver,
MultiplierName: multiplierName,
DenomsToClaim: denomsToClaim,
}
}
// Route return the message type used for routing the message.
func (msg MsgClaimSwapRewardVVesting) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimSwapRewardVVesting) Type() string {
return "claim_swap_reward_vvesting"
}
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimSwapRewardVVesting) ValidateBasic() error {
if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
}
if msg.Receiver.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be empty")
}
if err := MultiplierName(msg.MultiplierName).IsValid(); err != nil {
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
}
// GetSignBytes gets the canonical byte representation of the Msg.
func (msg MsgClaimSwapRewardVVesting) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// GetSigners returns the addresses of signers that must sign.
func (msg MsgClaimSwapRewardVVesting) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}

View File

@ -1,72 +1,522 @@
package types_test package types_test
import ( import (
"errors"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
type msgTest struct { func TestMsgClaimVVesting_Validate(t *testing.T) {
from sdk.AccAddress validAddress := sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1")))
multiplierName string
expectPass bool
}
type MsgTestSuite struct { type expectedErr struct {
suite.Suite wraps error
pass bool
tests []msgTest }
} type msgArgs struct {
sender sdk.AccAddress
func (suite *MsgTestSuite) SetupTest() { receiver sdk.AccAddress
tests := []msgTest{ multiplierName string
denomsToClaim []string
}
tests := []struct {
name string
msgArgs msgArgs
expect expectedErr
}{
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), name: "large multiplier is valid",
multiplierName: "large", msgArgs: msgArgs{
expectPass: true, sender: validAddress,
receiver: validAddress,
multiplierName: "large",
},
expect: expectedErr{
pass: true,
},
}, },
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), name: "medium multiplier is valid",
multiplierName: "medium", msgArgs: msgArgs{
expectPass: true, sender: validAddress,
receiver: validAddress,
multiplierName: "medium",
},
expect: expectedErr{
pass: true,
},
}, },
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), name: "small multiplier is valid",
multiplierName: "small", msgArgs: msgArgs{
expectPass: true, sender: validAddress,
receiver: validAddress,
multiplierName: "small",
},
expect: expectedErr{
pass: true,
},
}, },
{ {
from: sdk.AccAddress{}, name: "empty denoms to claim is valid",
multiplierName: "medium", msgArgs: msgArgs{
expectPass: false, sender: validAddress,
receiver: validAddress,
multiplierName: "small",
denomsToClaim: []string{},
},
expect: expectedErr{
pass: true,
},
}, },
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), name: "invalid sender",
multiplierName: "huge", msgArgs: msgArgs{
expectPass: false, 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",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "huge",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "Large",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "invalid claim denom",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "small",
denomsToClaim: []string{"a denom string that is invalid because it is much too long"},
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "too many claim denoms",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "small",
denomsToClaim: tooManyClaimDenoms(),
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
}, },
} }
suite.tests = tests
}
func (suite *MsgTestSuite) TestMsgValidation() { for _, tc := range tests {
for _, t := range suite.tests { msgs := []sdk.Msg{
msg := types.NewMsgClaimUSDXMintingReward(t.from, t.multiplierName) types.NewMsgClaimHardRewardVVesting(
err := msg.ValidateBasic() tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim,
if t.expectPass { ),
suite.Require().NoError(err) types.NewMsgClaimDelegatorRewardVVesting(
} else { tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim,
suite.Require().Error(err) ),
types.NewMsgClaimSwapRewardVVesting(
tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName, tc.msgArgs.denomsToClaim,
),
}
for _, msg := range msgs {
t.Run(msg.Type()+" "+tc.name, func(t *testing.T) {
err := msg.ValidateBasic()
if tc.expect.pass {
require.NoError(t, err)
} else {
require.Truef(t, errors.Is(err, tc.expect.wraps), "expected error '%s' was not actual '%s'", tc.expect.wraps, err)
}
})
} }
} }
} }
func TestMsgTestSuite(t *testing.T) { func TestMsgClaim_Validate(t *testing.T) {
suite.Run(t, new(MsgTestSuite)) validAddress := sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1")))
type expectedErr struct {
wraps error
pass bool
}
type msgArgs struct {
sender sdk.AccAddress
multiplierName string
denomsToClaim []string
}
tests := []struct {
name string
msgArgs msgArgs
expect expectedErr
}{
{
name: "large multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "large",
},
expect: expectedErr{
pass: true,
},
},
{
name: "medium multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "medium",
},
expect: expectedErr{
pass: true,
},
},
{
name: "small multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "small",
},
expect: expectedErr{
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",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "huge",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "Large",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "invalid claim denom",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "small",
denomsToClaim: []string{"a denom string that is invalid because it is much too long"},
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
{
name: "too many claim denoms",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "small",
denomsToClaim: tooManyClaimDenoms(),
},
expect: expectedErr{
wraps: types.ErrInvalidClaimDenoms,
},
},
}
for _, tc := range tests {
msgs := []sdk.Msg{
types.NewMsgClaimHardReward(
tc.msgArgs.sender, tc.msgArgs.multiplierName, 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 {
t.Run(msg.Type()+" "+tc.name, func(t *testing.T) {
err := msg.ValidateBasic()
if tc.expect.pass {
require.NoError(t, err)
} else {
require.Truef(t, errors.Is(err, tc.expect.wraps), "expected error '%s' was not actual '%s'", tc.expect.wraps, err)
}
})
}
}
}
func TestMsgClaimUSDXMintingRewardVVesting_Validate(t *testing.T) {
validAddress := sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1")))
type expectedErr struct {
wraps error
pass bool
}
type msgArgs struct {
sender sdk.AccAddress
receiver sdk.AccAddress
multiplierName string
}
tests := []struct {
name string
msgArgs msgArgs
expect expectedErr
}{
{
name: "large multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "large",
},
expect: expectedErr{
pass: true,
},
},
{
name: "medium multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "medium",
},
expect: expectedErr{
pass: true,
},
},
{
name: "small multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "small",
},
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",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "huge",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{
sender: validAddress,
receiver: validAddress,
multiplierName: "Large",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
msg := types.NewMsgClaimUSDXMintingRewardVVesting(tc.msgArgs.sender, tc.msgArgs.receiver, tc.msgArgs.multiplierName)
err := msg.ValidateBasic()
if tc.expect.pass {
require.NoError(t, err)
} else {
require.Truef(t, errors.Is(err, tc.expect.wraps), "expected error '%s' was not actual '%s'", tc.expect.wraps, err)
}
})
}
}
func TestMsgClaimUSDXMintingReward_Validate(t *testing.T) {
validAddress := sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1")))
type expectedErr struct {
wraps error
pass bool
}
type msgArgs struct {
sender sdk.AccAddress
multiplierName string
}
tests := []struct {
name string
msgArgs msgArgs
expect expectedErr
}{
{
name: "large multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "large",
},
expect: expectedErr{
pass: true,
},
},
{
name: "medium multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "medium",
},
expect: expectedErr{
pass: true,
},
},
{
name: "small multiplier is valid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "small",
},
expect: expectedErr{
pass: true,
},
},
{
name: "invalid sender",
msgArgs: msgArgs{
sender: sdk.AccAddress{},
multiplierName: "medium",
},
expect: expectedErr{
wraps: sdkerrors.ErrInvalidAddress,
},
},
{
name: "invalid multiplier",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "huge",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
{
name: "multiplier with capitalization is invalid",
msgArgs: msgArgs{
sender: validAddress,
multiplierName: "Large",
},
expect: expectedErr{
wraps: types.ErrInvalidMultiplier,
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
msg := types.NewMsgClaimUSDXMintingReward(tc.msgArgs.sender, tc.msgArgs.multiplierName)
err := msg.ValidateBasic()
if tc.expect.pass {
require.NoError(t, err)
} else {
require.Truef(t, errors.Is(err, tc.expect.wraps), "expected error '%s' was not actual '%s'", tc.expect.wraps, err)
}
})
}
}
func tooManyClaimDenoms() []string {
claimDenoms := make([]string, types.MaxDenomsToClaim+1)
for i := range claimDenoms {
claimDenoms[i] = fmt.Sprintf("denom%d", i)
}
return claimDenoms
} }

View File

@ -7,6 +7,7 @@ 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"
@ -52,7 +53,7 @@ type Params struct {
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" json:"swap_reward_periods"` SwapRewardPeriods MultiRewardPeriods `json:"swap_reward_periods" yaml:"swap_reward_periods"`
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
} }
@ -418,5 +419,5 @@ func (mn MultiplierName) IsValid() error {
case Small, Medium, Large: case Small, Medium, Large:
return nil return nil
} }
return fmt.Errorf("invalid multiplier name: %s", mn) return sdkerrors.Wrapf(ErrInvalidMultiplier, "invalid multiplier name: %s", mn)
} }

41
x/incentive/types/sdk.go Normal file
View File

@ -0,0 +1,41 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
)
// GetTotalVestingPeriodLength returns the summed length of all vesting periods
func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
length := int64(0)
for _, period := range periods {
length += period.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

@ -0,0 +1,170 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/kava-labs/kava/x/incentive/types"
)
func TestGetTotalVestingPeriodLength(t *testing.T) {
testCases := []struct {
name string
periods vesting.Periods
expectedVal int64
}{
{
name: "two period lengths are added together",
periods: vesting.Periods{
{
Length: 100,
},
{
Length: 200,
},
},
expectedVal: 300,
},
{
name: "no periods returns zero",
periods: nil,
expectedVal: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
length := types.GetTotalVestingPeriodLength(tc.periods)
require.Equal(t, tc.expectedVal, length)
})
}
}
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),
)
})
}
}

View File

@ -5,6 +5,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/swap/types" "github.com/kava-labs/kava/x/swap/types"
) )
@ -118,6 +119,8 @@ func (k Keeper) initializePool(ctx sdk.Context, poolID string, depositor sdk.Acc
k.SetPool(ctx, poolRecord) k.SetPool(ctx, poolRecord)
k.SetDepositorShares(ctx, shareRecord) k.SetDepositorShares(ctx, shareRecord)
k.hooks.AfterPoolDepositCreated(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
return pool.Reserves(), pool.TotalShares(), nil return pool.Reserves(), pool.TotalShares(), nil
} }
@ -131,8 +134,10 @@ func (k Keeper) addLiquidityToPool(ctx sdk.Context, record types.PoolRecord, dep
poolRecord := types.NewPoolRecord(pool) poolRecord := types.NewPoolRecord(pool)
shareRecord, found := k.GetDepositorShares(ctx, depositor, poolRecord.PoolID) shareRecord, sharesFound := k.GetDepositorShares(ctx, depositor, poolRecord.PoolID)
if found { if sharesFound {
k.hooks.BeforePoolDepositModified(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
shareRecord.SharesOwned = shareRecord.SharesOwned.Add(shares) shareRecord.SharesOwned = shareRecord.SharesOwned.Add(shares)
} else { } else {
shareRecord = types.NewShareRecord(depositor, poolRecord.PoolID, shares) shareRecord = types.NewShareRecord(depositor, poolRecord.PoolID, shares)
@ -141,5 +146,9 @@ func (k Keeper) addLiquidityToPool(ctx sdk.Context, record types.PoolRecord, dep
k.SetPool(ctx, poolRecord) k.SetPool(ctx, poolRecord)
k.SetDepositorShares(ctx, shareRecord) k.SetDepositorShares(ctx, shareRecord)
if !sharesFound {
k.hooks.AfterPoolDepositCreated(ctx, poolRecord.PoolID, depositor, shareRecord.SharesOwned)
}
return depositAmount, shares, nil return depositAmount, shares, nil
} }

View File

@ -6,21 +6,9 @@ import (
"github.com/kava-labs/kava/x/swap/types" "github.com/kava-labs/kava/x/swap/types"
) )
func (k Keeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) {
// FIXME return pool shares once merged with acceptance branch
return sdk.Int{}, false
}
func (k *Keeper) GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
// FIXME return depositor shares once merged with acceptance branch
return sdk.Int{}, false
}
// Implements SwapHooks interface // Implements SwapHooks interface
var _ types.SwapHooks = Keeper{} var _ types.SwapHooks = Keeper{}
// FIXME call hooks within pool logic
// AfterPoolDepositCreated - call hook if registered // AfterPoolDepositCreated - call hook if registered
func (k Keeper) AfterPoolDepositCreated(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharesOwned sdk.Int) { func (k Keeper) AfterPoolDepositCreated(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharesOwned sdk.Int) {
if k.hooks != nil { if k.hooks != nil {

View File

@ -117,6 +117,15 @@ func (k Keeper) GetAllPools(ctx sdk.Context) (records types.PoolRecords) {
return return
} }
// GetPoolShares gets the total shares in a pool from the store
func (k Keeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) {
pool, found := k.GetPool(ctx, poolID)
if !found {
return sdk.Int{}, false
}
return pool.TotalShares, true
}
// GetDepositorShares gets a share record from the store // GetDepositorShares gets a share record from the store
func (k Keeper) GetDepositorShares(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (types.ShareRecord, bool) { func (k Keeper) GetDepositorShares(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (types.ShareRecord, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositorPoolSharesPrefix)
@ -187,3 +196,12 @@ func (k Keeper) GetAllDepositorSharesByOwner(ctx sdk.Context, owner sdk.AccAddre
}) })
return return
} }
// GetDepositorSharesAmount gets a depositor's shares in a pool from the store
func (k *Keeper) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
record, found := k.GetDepositorShares(ctx, depositor, poolID)
if !found {
return sdk.Int{}, false
}
return record.SharesOwned, true
}

View File

@ -96,6 +96,10 @@ func (suite *keeperTestSuite) TestPool_Persistance() {
suite.True(ok) suite.True(ok)
suite.Equal(record, savedRecord) suite.Equal(record, savedRecord)
savedShares, ok := suite.Keeper.GetPoolShares(suite.Ctx, record.PoolID)
suite.True(ok)
suite.Equal(record.TotalShares, savedShares)
suite.Keeper.DeletePool(suite.Ctx, record.PoolID) suite.Keeper.DeletePool(suite.Ctx, record.PoolID)
deletedPool, ok := suite.Keeper.GetPool(suite.Ctx, record.PoolID) deletedPool, ok := suite.Keeper.GetPool(suite.Ctx, record.PoolID)
suite.False(ok) suite.False(ok)
@ -114,6 +118,10 @@ func (suite *keeperTestSuite) TestShare_Persistance() {
suite.True(ok) suite.True(ok)
suite.Equal(record, savedRecord) suite.Equal(record, savedRecord)
savedShares, ok := suite.Keeper.GetDepositorSharesAmount(suite.Ctx, depositor, poolID)
suite.True(ok)
suite.Equal(record.SharesOwned, savedShares)
suite.Keeper.DeleteDepositorShares(suite.Ctx, depositor, poolID) suite.Keeper.DeleteDepositorShares(suite.Ctx, depositor, poolID)
deletedShares, ok := suite.Keeper.GetDepositorShares(suite.Ctx, depositor, poolID) deletedShares, ok := suite.Keeper.GetDepositorShares(suite.Ctx, depositor, poolID)
suite.False(ok) suite.False(ok)

View File

@ -52,6 +52,8 @@ func (k Keeper) Withdraw(ctx sdk.Context, owner sdk.AccAddress, shares sdk.Int,
} }
k.updatePool(ctx, poolID, pool) k.updatePool(ctx, poolID, pool)
k.hooks.BeforePoolDepositModified(ctx, poolID, owner, shareRecord.SharesOwned)
k.updateShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares)) k.updateShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares))
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount) err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount)