[R4R] re-enable claiming rewards validator vesting accounts (#920)

* feat: add new msg types for claim rewards from validator vesting accounts

* fix: validate owner is validator vesting account

* feat: add validator vesting tests for incent claims

* address review comments

* fix: client command name and example
This commit is contained in:
Kevin Davis 2021-06-07 08:04:32 -06:00 committed by GitHub
parent fc85052522
commit 4d6f6aab3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 988 additions and 129 deletions

View File

@ -1,154 +1,175 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/incentive/types
// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper
package incentive package incentive
// DO NOT EDIT - generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen)
import ( import (
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
const ( const (
BeginningOfMonth = keeper.BeginningOfMonth USDXMintingClaimType = types.USDXMintingClaimType
MidMonth = keeper.MidMonth HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType
PaymentHour = keeper.PaymentHour BondDenom = types.BondDenom
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount EventTypeClaim = types.EventTypeClaim
AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod EventTypeRewardPeriod = types.EventTypeRewardPeriod
AttributeKeyClaimType = types.AttributeKeyClaimType EventTypeClaimPeriod = types.EventTypeClaimPeriod
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod AttributeValueCategory = types.AttributeValueCategory
AttributeValueCategory = types.AttributeValueCategory AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
BondDenom = types.BondDenom AttributeKeyClaimAmount = types.AttributeKeyClaimAmount
DefaultParamspace = types.DefaultParamspace AttributeKeyClaimType = types.AttributeKeyClaimType
EventTypeClaim = types.EventTypeClaim AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
EventTypeClaimPeriod = types.EventTypeClaimPeriod AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod
EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry ModuleName = types.ModuleName
EventTypeRewardPeriod = types.EventTypeRewardPeriod StoreKey = types.StoreKey
HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType RouterKey = types.RouterKey
Large = types.Large DefaultParamspace = types.DefaultParamspace
Medium = types.Medium QuerierRoute = types.QuerierRoute
ModuleName = types.ModuleName Small = types.Small
QuerierRoute = types.QuerierRoute Medium = types.Medium
QueryGetClaimPeriods = types.QueryGetClaimPeriods Large = types.Large
QueryGetHardRewards = types.QueryGetHardRewards QueryGetRewards = types.QueryGetRewards
QueryGetParams = types.QueryGetParams QueryGetHardRewards = types.QueryGetHardRewards
QueryGetRewardPeriods = types.QueryGetRewardPeriods QueryGetHardRewardsUnsynced = types.QueryGetHardRewardsUnsynced
QueryGetRewards = types.QueryGetRewards QueryGetUSDXMintingRewards = types.QueryGetUSDXMintingRewards
QueryGetUSDXMintingRewards = types.QueryGetUSDXMintingRewards QueryGetUSDXMintingRewardsUnsynced = types.QueryGetUSDXMintingRewardsUnsynced
RestClaimCollateralType = types.RestClaimCollateralType QueryGetRewardFactors = types.QueryGetRewardFactors
RestClaimOwner = types.RestClaimOwner QueryGetParams = types.QueryGetParams
RestClaimType = types.RestClaimType QueryGetRewardPeriods = types.QueryGetRewardPeriods
RouterKey = types.RouterKey QueryGetClaimPeriods = types.QueryGetClaimPeriods
Small = types.Small RestClaimCollateralType = types.RestClaimCollateralType
StoreKey = types.StoreKey RestClaimOwner = types.RestClaimOwner
USDXMintingClaimType = types.USDXMintingClaimType RestClaimType = types.RestClaimType
RestUnsynced = types.RestUnsynced
BeginningOfMonth = keeper.BeginningOfMonth
MidMonth = keeper.MidMonth
PaymentHour = keeper.PaymentHour
) )
var ( var (
// function aliases // functions aliases
CalculateTimeElapsed = keeper.CalculateTimeElapsed GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
NewKeeper = keeper.NewKeeper NewUSDXMintingClaim = types.NewUSDXMintingClaim
NewQuerier = keeper.NewQuerier NewHardLiquidityProviderClaim = types.NewHardLiquidityProviderClaim
DefaultGenesisState = types.DefaultGenesisState NewMultiRewardPeriod = types.NewMultiRewardPeriod
DefaultParams = types.DefaultParams NewRewardIndex = types.NewRewardIndex
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength NewMultiRewardIndex = types.NewMultiRewardIndex
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime RegisterCodec = types.RegisterCodec
NewGenesisState = types.NewGenesisState NewGenesisState = types.NewGenesisState
NewHardLiquidityProviderClaim = types.NewHardLiquidityProviderClaim DefaultGenesisState = types.DefaultGenesisState
NewMsgClaimHardReward = types.NewMsgClaimHardReward NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
NewMultiRewardIndex = types.NewMultiRewardIndex NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting
NewMultiRewardPeriod = types.NewMultiRewardPeriod NewMsgClaimHardReward = types.NewMsgClaimHardReward
NewMultiplier = types.NewMultiplier NewMsgClaimHardRewardVVesting = types.NewMsgClaimHardRewardVVesting
NewParams = types.NewParams NewParams = types.NewParams
NewPeriod = types.NewPeriod DefaultParams = types.DefaultParams
NewQueryHardRewardsParams = types.NewQueryHardRewardsParams ParamKeyTable = types.ParamKeyTable
NewQueryRewardsParams = types.NewQueryRewardsParams NewRewardPeriod = types.NewRewardPeriod
NewQueryUSDXMintingRewardsParams = types.NewQueryUSDXMintingRewardsParams NewMultiplier = types.NewMultiplier
NewRewardIndex = types.NewRewardIndex NewPeriod = types.NewPeriod
NewRewardPeriod = types.NewRewardPeriod NewQueryRewardsParams = types.NewQueryRewardsParams
NewUSDXMintingClaim = types.NewUSDXMintingClaim NewQueryUSDXMintingRewardsParams = types.NewQueryUSDXMintingRewardsParams
ParamKeyTable = types.ParamKeyTable NewQueryUSDXMintingRewardsUnsyncedParams = types.NewQueryUSDXMintingRewardsUnsyncedParams
RegisterCodec = types.RegisterCodec NewQueryHardRewardsParams = types.NewQueryHardRewardsParams
NewQueryHardRewardsUnsyncedParams = types.NewQueryHardRewardsUnsyncedParams
NewQueryRewardFactorsParams = types.NewQueryRewardFactorsParams
NewRewardFactor = types.NewRewardFactor
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
CalculateTimeElapsed = keeper.CalculateTimeElapsed
// variable aliases // variable aliases
DefaultActive = types.DefaultActive ModuleCdc = types.ModuleCdc
DefaultClaimEnd = types.DefaultClaimEnd
DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes
DefaultHardClaims = types.DefaultHardClaims
DefaultMultiRewardPeriods = types.DefaultMultiRewardPeriods
DefaultMultipliers = types.DefaultMultipliers
DefaultRewardPeriods = types.DefaultRewardPeriods
DefaultUSDXClaims = types.DefaultUSDXClaims
ErrAccountNotFound = types.ErrAccountNotFound
ErrClaimExpired = types.ErrClaimExpired
ErrClaimNotFound = types.ErrClaimNotFound ErrClaimNotFound = types.ErrClaimNotFound
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
ErrInvalidAccountType = types.ErrInvalidAccountType
ErrInvalidClaimType = types.ErrInvalidClaimType
ErrInvalidMultiplier = types.ErrInvalidMultiplier
ErrNoClaimsFound = types.ErrNoClaimsFound
ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound
ErrInvalidAccountType = types.ErrInvalidAccountType
ErrNoClaimsFound = types.ErrNoClaimsFound
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
ErrAccountNotFound = types.ErrAccountNotFound
ErrInvalidMultiplier = types.ErrInvalidMultiplier
ErrZeroClaim = types.ErrZeroClaim ErrZeroClaim = types.ErrZeroClaim
GovDenom = types.GovDenom ErrClaimExpired = types.ErrClaimExpired
HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix ErrInvalidClaimType = types.ErrInvalidClaimType
HardDelegatorRewardFactorKeyPrefix = types.HardDelegatorRewardFactorKeyPrefix ErrInvalidClaimOwner = types.ErrInvalidClaimOwner
USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix
USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix
PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix
HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix
HardLiquidityRewardDenom = types.HardLiquidityRewardDenom
HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix
IncentiveMacc = types.IncentiveMacc PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix
KeyClaimEnd = types.KeyClaimEnd HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix
PreviousHardBorrowRewardAccrualTimeKeyPrefix = types.PreviousHardBorrowRewardAccrualTimeKeyPrefix
HardDelegatorRewardFactorKeyPrefix = types.HardDelegatorRewardFactorKeyPrefix
PreviousHardDelegatorRewardAccrualTimeKeyPrefix = types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix
USDXMintingRewardDenom = types.USDXMintingRewardDenom
HardLiquidityRewardDenom = types.HardLiquidityRewardDenom
KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods
KeyHardSupplyRewardPeriods = types.KeyHardSupplyRewardPeriods
KeyHardBorrowRewardPeriods = types.KeyHardBorrowRewardPeriods KeyHardBorrowRewardPeriods = types.KeyHardBorrowRewardPeriods
KeyHardDelegatorRewardPeriods = types.KeyHardDelegatorRewardPeriods KeyHardDelegatorRewardPeriods = types.KeyHardDelegatorRewardPeriods
KeyHardSupplyRewardPeriods = types.KeyHardSupplyRewardPeriods KeyClaimEnd = types.KeyClaimEnd
KeyMultipliers = types.KeyMultipliers KeyMultipliers = types.KeyMultipliers
KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods DefaultActive = types.DefaultActive
ModuleCdc = types.ModuleCdc DefaultRewardPeriods = types.DefaultRewardPeriods
PreviousHardBorrowRewardAccrualTimeKeyPrefix = types.PreviousHardBorrowRewardAccrualTimeKeyPrefix DefaultMultiRewardPeriods = types.DefaultMultiRewardPeriods
PreviousHardDelegatorRewardAccrualTimeKeyPrefix = types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix DefaultMultipliers = types.DefaultMultipliers
PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix DefaultUSDXClaims = types.DefaultUSDXClaims
PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix DefaultHardClaims = types.DefaultHardClaims
DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes
DefaultClaimEnd = types.DefaultClaimEnd
GovDenom = types.GovDenom
PrincipalDenom = types.PrincipalDenom PrincipalDenom = types.PrincipalDenom
USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix IncentiveMacc = types.IncentiveMacc
USDXMintingRewardDenom = types.USDXMintingRewardDenom
USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix
) )
type ( type (
Hooks = keeper.Hooks Claim = types.Claim
Keeper = keeper.Keeper Claims = types.Claims
AccountKeeper = types.AccountKeeper BaseClaim = types.BaseClaim
BaseClaim = types.BaseClaim BaseMultiClaim = types.BaseMultiClaim
BaseMultiClaim = types.BaseMultiClaim USDXMintingClaim = types.USDXMintingClaim
CDPHooks = types.CDPHooks USDXMintingClaims = types.USDXMintingClaims
CdpKeeper = types.CdpKeeper HardLiquidityProviderClaim = types.HardLiquidityProviderClaim
Claim = types.Claim HardLiquidityProviderClaims = types.HardLiquidityProviderClaims
Claims = types.Claims MultiRewardPeriod = types.MultiRewardPeriod
GenesisAccumulationTime = types.GenesisAccumulationTime MultiRewardPeriods = types.MultiRewardPeriods
GenesisAccumulationTimes = types.GenesisAccumulationTimes RewardIndex = types.RewardIndex
GenesisState = types.GenesisState RewardIndexes = types.RewardIndexes
HARDHooks = types.HARDHooks MultiRewardIndex = types.MultiRewardIndex
HardKeeper = types.HardKeeper MultiRewardIndexes = types.MultiRewardIndexes
HardLiquidityProviderClaim = types.HardLiquidityProviderClaim SupplyKeeper = types.SupplyKeeper
HardLiquidityProviderClaims = types.HardLiquidityProviderClaims StakingKeeper = types.StakingKeeper
MsgClaimHardReward = types.MsgClaimHardReward CdpKeeper = types.CdpKeeper
MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward HardKeeper = types.HardKeeper
MultiRewardIndex = types.MultiRewardIndex AccountKeeper = types.AccountKeeper
MultiRewardIndexes = types.MultiRewardIndexes CDPHooks = types.CDPHooks
MultiRewardPeriod = types.MultiRewardPeriod HARDHooks = types.HARDHooks
MultiRewardPeriods = types.MultiRewardPeriods GenesisState = types.GenesisState
Multiplier = types.Multiplier GenesisAccumulationTime = types.GenesisAccumulationTime
MultiplierName = types.MultiplierName GenesisAccumulationTimes = types.GenesisAccumulationTimes
Multipliers = types.Multipliers MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward
Params = types.Params MsgClaimUSDXMintingRewardVVesting = types.MsgClaimUSDXMintingRewardVVesting
QueryHardRewardsParams = types.QueryHardRewardsParams MsgClaimHardReward = types.MsgClaimHardReward
QueryRewardsParams = types.QueryRewardsParams MsgClaimHardRewardVVesting = types.MsgClaimHardRewardVVesting
QueryUSDXMintingRewardsParams = types.QueryUSDXMintingRewardsParams Params = types.Params
RewardIndex = types.RewardIndex RewardPeriod = types.RewardPeriod
RewardIndexes = types.RewardIndexes RewardPeriods = types.RewardPeriods
RewardPeriod = types.RewardPeriod Multiplier = types.Multiplier
RewardPeriods = types.RewardPeriods Multipliers = types.Multipliers
StakingKeeper = types.StakingKeeper MultiplierName = types.MultiplierName
SupplyKeeper = types.SupplyKeeper QueryRewardsParams = types.QueryRewardsParams
USDXMintingClaim = types.USDXMintingClaim QueryUSDXMintingRewardsParams = types.QueryUSDXMintingRewardsParams
USDXMintingClaims = types.USDXMintingClaims QueryUSDXMintingRewardsUnsyncedParams = types.QueryUSDXMintingRewardsUnsyncedParams
QueryHardRewardsParams = types.QueryHardRewardsParams
QueryHardRewardsUnsyncedParams = types.QueryHardRewardsUnsyncedParams
QueryRewardFactorsParams = types.QueryRewardFactorsParams
RewardFactor = types.RewardFactor
RewardFactors = types.RewardFactors
Hooks = keeper.Hooks
Keeper = keeper.Keeper
) )

View File

@ -27,7 +27,9 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
incentiveTxCmd.AddCommand(flags.PostCommands( incentiveTxCmd.AddCommand(flags.PostCommands(
getCmdClaimCdp(cdc), getCmdClaimCdp(cdc),
getCmdClaimCdpVVesting(cdc),
getCmdClaimHard(cdc), getCmdClaimHard(cdc),
getCmdClaimHardVVesting(cdc),
)...) )...)
return incentiveTxCmd return incentiveTxCmd
@ -63,6 +65,76 @@ func getCmdClaimCdp(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:
$ %s tx %s claim-cdp-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.NewMsgClaimUSDXMintingRewardVVesting(sender, receiver, multiplier)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func getCmdClaimHardVVesting(cdc *codec.Codec) *cobra.Command {
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{ return &cobra.Command{
Use: "claim-hard [multiplier]", Use: "claim-hard [multiplier]",

View File

@ -26,3 +26,11 @@ type PostClaimReq 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"`
} }
// PostClaimReq defines the properties of claim transaction's request body.
type PostClaimVVestingReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}

View File

@ -17,7 +17,10 @@ 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", postClaimCdpHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-cdp-vesting", postClaimCdpVVestingHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-hard", postClaimHardHandlerFn(cliCtx)).Methods("POST") r.HandleFunc("/incentive/claim-hard", postClaimHardHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-hard-vesting", postClaimHardVVestingHandlerFn(cliCtx)).Methods("POST")
} }
func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
@ -53,6 +56,39 @@ func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
} }
} }
func postClaimCdpVVestingHandlerFn(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.NewMsgClaimUSDXMintingRewardVVesting(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 postClaimHardHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func postClaimHardHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var requestBody PostClaimReq var requestBody PostClaimReq
@ -85,3 +121,36 @@ func postClaimHardHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg}) 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})
}
}

View File

@ -15,8 +15,12 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
switch msg := msg.(type) { switch msg := msg.(type) {
case types.MsgClaimUSDXMintingReward: case types.MsgClaimUSDXMintingReward:
return handleMsgClaimUSDXMintingReward(ctx, k, msg) return handleMsgClaimUSDXMintingReward(ctx, k, msg)
case types.MsgClaimUSDXMintingRewardVVesting:
return handleMsgClaimUSDXMintingRewardVVesting(ctx, k, msg)
case types.MsgClaimHardReward: case types.MsgClaimHardReward:
return handleMsgClaimHardReward(ctx, k, msg) return handleMsgClaimHardReward(ctx, k, msg)
case types.MsgClaimHardRewardVVesting:
return handleMsgClaimHardRewardVVesting(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)
} }
@ -24,7 +28,6 @@ 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, types.MultiplierName(msg.MultiplierName))
if err != nil { if err != nil {
return nil, err return nil, err
@ -34,6 +37,17 @@ func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types
}, nil }, nil
} }
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 != nil {
return nil, err
}
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
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, types.MultiplierName(msg.MultiplierName))
@ -44,3 +58,14 @@ func handleMsgClaimHardReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgCla
Events: ctx.EventManager().Events(), Events: ctx.EventManager().Events(),
}, nil }, nil
} }
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 != nil {
return nil, err
}
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}

View File

@ -74,6 +74,66 @@ func (k Keeper) ClaimUSDXMintingReward(ctx sdk.Context, addr sdk.AccAddress, mul
return nil 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 // 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 { func (k Keeper) ClaimHardReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
_, found := k.GetHardLiquidityProviderClaim(ctx, addr) _, found := k.GetHardLiquidityProviderClaim(ctx, addr)
@ -133,6 +193,74 @@ func (k Keeper) ClaimHardReward(ctx sdk.Context, addr sdk.AccAddress, multiplier
return nil return nil
} }
// ClaimHardReward 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
}
// 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

@ -155,6 +155,156 @@ func (suite *KeeperTestSuite) TestPayoutUSDXMintingClaim() {
} }
} }
func (suite *KeeperTestSuite) TestPayoutUSDXMintingClaimVVesting() {
type args struct {
ctype string
rewardsPerSecond sdk.Coin
initialTime time.Time
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),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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("ukava", 11571385600)),
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),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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(),
expectedPeriods: vesting.Periods{},
isPeriodicVestingAccount: false,
},
errArgs{
expectPass: false,
contains: "claim amount rounds to zero",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupWithGenState()
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
// setup incentive state
params := types.NewParams(
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))},
types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
tc.args.multipliers,
tc.args.initialTime.Add(time.Hour*24*365*5),
)
suite.keeper.SetParams(suite.ctx, params)
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
// sets addrs[2] to be a validator vesting account
ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[2])
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), suite.ctx.BlockTime().Unix()+16)
suite.Require().NoError(err2)
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.ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90)
ak.SetAccount(suite.ctx, vva)
// setup account state
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral))
suite.Require().NoError(err)
err = sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[2], sdk.NewCoins(tc.args.initialCollateral))
suite.Require().NoError(err)
// setup kavadist state
err = sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
suite.Require().NoError(err)
// 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)
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()) // TODO check balance change to decouple from initialized account balance.
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, 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 *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() { func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() {
type args struct { type args struct {
deposit sdk.Coins deposit sdk.Coins
@ -431,6 +581,296 @@ func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() {
} }
} }
func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaimVVesting() {
type args struct {
deposit sdk.Coins
borrow sdk.Coins
rewardsPerSecond sdk.Coins
initialTime time.Time
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)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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: "",
},
},
{
"invalid zero rewards",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: cs(c("hard", 0)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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", 0)),
expectedPeriods: vesting.Periods{},
isPeriodicVestingAccount: false,
},
errArgs{
expectPass: false,
contains: "claim amount rounds to zero",
},
},
{
"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)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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)),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
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() {
suite.SetupWithGenState()
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
// setup kavadist state
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("hard", 1000000000000000000), c("ukava", 1000000000000000000)))
suite.Require().NoError(err)
// Set up generic reward periods
var multiRewardPeriods types.MultiRewardPeriods
var rewardPeriods types.RewardPeriods
for _, coin := range tc.args.deposit {
if len(tc.args.rewardsPerSecond) > 0 {
rewardPeriod := types.NewRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])
rewardPeriods = append(rewardPeriods, rewardPeriod)
}
multiRewardPeriod := types.NewMultiRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)
multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod)
}
// Set up generic reward periods
params := types.NewParams(
rewardPeriods, multiRewardPeriods, multiRewardPeriods, rewardPeriods,
types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
tc.args.initialTime.Add(time.Hour*24*365*5),
)
suite.keeper.SetParams(suite.ctx, params)
// Set each denom's previous accrual time and supply reward factor
if len(tc.args.rewardsPerSecond) > 0 {
for _, coin := range tc.args.deposit {
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime)
var rewardIndexes types.RewardIndexes
for _, rewardCoin := range tc.args.rewardsPerSecond {
rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
rewardIndexes = append(rewardIndexes, rewardIndex)
}
suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, coin.Denom, rewardIndexes)
}
}
// Set each denom's previous accrual time and borrow reward factor
if len(tc.args.rewardsPerSecond) > 0 {
for _, coin := range tc.args.borrow {
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime)
var rewardIndexes types.RewardIndexes
for _, rewardCoin := range tc.args.rewardsPerSecond {
rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
rewardIndexes = append(rewardIndexes, rewardIndex)
}
suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, coin.Denom, rewardIndexes)
}
}
// sets addrs[3] to be a validator vesting account
ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[3])
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), suite.ctx.BlockTime().Unix()+16)
suite.Require().NoError(err2)
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.ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90)
ak.SetAccount(suite.ctx, vva)
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
// 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, suite.addrs[3])
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, suite.addrs[3])
suite.Require().True(found)
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
// Sync hard borrow rewards
borrow, found := suite.hardKeeper.GetBorrow(suite.ctx, suite.addrs[3])
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, suite.addrs[3], 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())
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, 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 *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() { func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
type accountArgs struct { type accountArgs struct {
periods vesting.Periods periods vesting.Periods

View File

@ -21,4 +21,6 @@ func RegisterCodec(cdc *codec.Codec) {
// 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(MsgClaimUSDXMintingRewardVVesting{}, "incentive/MsgClaimUSDXRewardVVesting", nil)
cdc.RegisterConcrete(MsgClaimHardRewardVVesting{}, "incentive/MsgClaimHardRewardVVesting", nil)
} }

View File

@ -10,6 +10,8 @@ import (
// 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 = &MsgClaimHardReward{} var _ sdk.Msg = &MsgClaimHardReward{}
var _ sdk.Msg = &MsgClaimHardRewardVVesting{}
var _ sdk.Msg = &MsgClaimUSDXMintingRewardVVesting{}
// MsgClaimUSDXMintingReward message type used to claim USDX minting rewards // MsgClaimUSDXMintingReward message type used to claim USDX minting rewards
type MsgClaimUSDXMintingReward struct { type MsgClaimUSDXMintingReward struct {
@ -50,6 +52,52 @@ func (msg MsgClaimUSDXMintingReward) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender} return []sdk.AccAddress{msg.Sender}
} }
// MsgClaimUSDXMintingRewardVVesting message type used to claim USDX minting rewards for validator vesting accounts
type MsgClaimUSDXMintingRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
// NewMsgClaimUSDXMintingRewardVVesting returns a new MsgClaimUSDXMintingReward.
func NewMsgClaimUSDXMintingRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string) MsgClaimUSDXMintingRewardVVesting {
return MsgClaimUSDXMintingRewardVVesting{
Sender: sender,
Receiver: receiver,
MultiplierName: multiplierName,
}
}
// Route return the message type used for routing the message.
func (msg MsgClaimUSDXMintingRewardVVesting) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimUSDXMintingRewardVVesting) Type() string {
return "claim_usdx_minting_reward_vvesting"
}
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimUSDXMintingRewardVVesting) 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")
}
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
}
// GetSignBytes gets the canonical byte representation of the Msg.
func (msg MsgClaimUSDXMintingRewardVVesting) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// GetSigners returns the addresses of signers that must sign.
func (msg MsgClaimUSDXMintingRewardVVesting) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}
// MsgClaimHardReward message type used to claim Hard liquidity provider rewards // MsgClaimHardReward message type used to claim Hard liquidity provider rewards
type MsgClaimHardReward struct { type MsgClaimHardReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
@ -90,3 +138,49 @@ func (msg MsgClaimHardReward) GetSignBytes() []byte {
func (msg MsgClaimHardReward) GetSigners() []sdk.AccAddress { func (msg MsgClaimHardReward) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender} return []sdk.AccAddress{msg.Sender}
} }
// MsgClaimHardRewardVVesting message type used to claim Hard liquidity provider rewards for validator vesting accounts
type MsgClaimHardRewardVVesting struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
// NewMsgClaimHardRewardVVesting returns a new MsgClaimHardReward.
func NewMsgClaimHardRewardVVesting(sender, receiver sdk.AccAddress, multiplierName string) MsgClaimHardRewardVVesting {
return MsgClaimHardRewardVVesting{
Sender: sender,
Receiver: receiver,
MultiplierName: multiplierName,
}
}
// Route return the message type used for routing the message.
func (msg MsgClaimHardRewardVVesting) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimHardRewardVVesting) Type() string {
return "claim_hard_reward_vvesting"
}
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimHardRewardVVesting) 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")
}
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
}
// GetSignBytes gets the canonical byte representation of the Msg.
func (msg MsgClaimHardRewardVVesting) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// GetSigners returns the addresses of signers that must sign.
func (msg MsgClaimHardRewardVVesting) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}