Incentive PR 4: claim Hard rewards via the Incentive module (#780)

* claim hard reward keeper methods

* test hard claim payout

* claim hard rewards via cli

* query hard claims via cli

* rest txs and queries

* add handler test

* add claim type event field
This commit is contained in:
Denali Marsh 2021-01-26 12:52:34 +01:00 committed by GitHub
parent 72a6df17fd
commit 3a08fc582b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 610 additions and 72 deletions

View File

@ -23,7 +23,8 @@ const (
ModuleName = types.ModuleName
QuerierRoute = types.QuerierRoute
QueryGetClaimPeriods = types.QueryGetClaimPeriods
QueryGetClaims = types.QueryGetClaims
QueryGetCdpClaims = types.QueryGetCdpClaims
QueryGetHardClaims = types.QueryGetHardClaims
QueryGetParams = types.QueryGetParams
QueryGetRewardPeriods = types.QueryGetRewardPeriods
RestClaimCollateralType = types.RestClaimCollateralType
@ -35,24 +36,27 @@ const (
var (
// function aliases
CalculateTimeElapsed = keeper.CalculateTimeElapsed
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
NewGenesisState = types.NewGenesisState
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
NewMultiplier = types.NewMultiplier
NewParams = types.NewParams
NewPeriod = types.NewPeriod
NewQueryClaimsParams = types.NewQueryClaimsParams
NewRewardIndex = types.NewRewardIndex
NewRewardPeriod = types.NewRewardPeriod
NewUSDXMintingClaim = types.NewUSDXMintingClaim
ParamKeyTable = types.ParamKeyTable
RegisterCodec = types.RegisterCodec
CalculateTimeElapsed = keeper.CalculateTimeElapsed
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
NewGenesisState = types.NewGenesisState
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
NewMsgClaimHardLiquidityProviderReward = types.NewMsgClaimHardLiquidityProviderReward
NewMultiplier = types.NewMultiplier
NewParams = types.NewParams
NewPeriod = types.NewPeriod
NewQueryCdpClaimsParams = types.NewQueryCdpClaimsParams
NewQueryHardClaimsParams = types.NewQueryHardClaimsParams
NewRewardIndex = types.NewRewardIndex
NewRewardPeriod = types.NewRewardPeriod
NewUSDXMintingClaim = types.NewUSDXMintingClaim
NewHardLiquidityProviderClaim = types.NewHardLiquidityProviderClaim
ParamKeyTable = types.ParamKeyTable
RegisterCodec = types.RegisterCodec
// variable aliases
PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix
@ -99,7 +103,8 @@ type (
Multipliers = types.Multipliers
Params = types.Params
PostClaimReq = types.PostClaimReq
QueryClaimsParams = types.QueryClaimsParams
QueryCdpClaimsParams = types.QueryCdpClaimsParams
QueryHardClaimsParams = types.QueryHardClaimsParams
RewardIndex = types.RewardIndex
RewardIndexes = types.RewardIndexes
RewardPeriod = types.RewardPeriod

View File

@ -25,7 +25,8 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
incentiveQueryCmd.AddCommand(flags.GetCommands(
queryParamsCmd(queryRoute, cdc),
queryClaimsCmd(queryRoute, cdc),
queryCdpClaimsCmd(queryRoute, cdc),
queryHardClaimsCmd(queryRoute, cdc),
)...)
return incentiveQueryCmd
@ -35,16 +36,16 @@ const (
flagOwner = "owner"
)
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
func queryCdpClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "claims ",
Use: "cdp-claims",
Short: "query USDX minting claims",
Long: strings.TrimSpace(
fmt.Sprintf(`Query USDX minting claims with optional flag for finding claims for a specifc owner
Example:
$ %s query %s claims
$ %s query %s claims --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
$ %s query %s cdp-claims
$ %s query %s cdp-claims --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
`,
version.ClientName, types.ModuleName, version.ClientName, types.ModuleName)),
Args: cobra.NoArgs,
@ -60,14 +61,67 @@ func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
if err != nil {
return err
}
params := types.NewQueryClaimsParams(page, limit, owner)
params := types.NewQueryCdpClaimsParams(page, limit, owner)
bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}
// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaims)
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdpClaims)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
cliCtx = cliCtx.WithHeight(height)
var claims types.USDXMintingClaims
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
return fmt.Errorf("failed to unmarshal claims: %w", err)
}
return cliCtx.PrintOutput(claims)
},
}
cmd.Flags().String(flagOwner, "", "(optional) filter by claim owner address")
cmd.Flags().Int(flags.FlagPage, 1, "pagination page of CDPs to to query for")
cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of CDPs to query for")
return cmd
}
func queryHardClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "hard-claims",
Short: "query Hard liquidity provider claims",
Long: strings.TrimSpace(
fmt.Sprintf(`Query Hard liquidity provider claims with optional flag for finding claims for a specifc owner
Example:
$ %s query %s hard-claims
$ %s query %s hard-claims --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
`,
version.ClientName, types.ModuleName, version.ClientName, types.ModuleName)),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
strOwner := viper.GetString(flagOwner)
page := viper.GetInt(flags.FlagPage)
limit := viper.GetInt(flags.FlagLimit)
// Prepare params for querier
owner, err := sdk.AccAddressFromBech32(strOwner)
if err != nil {
return err
}
params := types.NewQueryHardClaimsParams(page, limit, owner)
bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}
// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetHardClaims)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err

View File

@ -26,22 +26,23 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
}
incentiveTxCmd.AddCommand(flags.PostCommands(
getCmdClaim(cdc),
getCmdClaimCdp(cdc),
getCmdClaimHard(cdc),
)...)
return incentiveTxCmd
}
func getCmdClaim(cdc *codec.Codec) *cobra.Command {
func getCmdClaimCdp(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim [owner] [multiplier]",
Short: "claim rewards for cdp owner and collateral-type",
Use: "claim-cdp [owner] [multiplier]",
Short: "claim CDP rewards for cdp owner and collateral-type",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input collateral-type and multiplier,
fmt.Sprintf(`Claim any outstanding CDP rewards owned by owner for the input collateral-type and multiplier,
Example:
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw large
$ %s tx %s claim-cdp kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw large
`, version.ClientName, types.ModuleName),
),
Args: cobra.ExactArgs(2),
@ -63,3 +64,34 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
},
}
}
func getCmdClaimHard(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim-hard [owner] [multiplier]",
Short: "claim Hard rewards for deposit/borrow and delegating",
Long: strings.TrimSpace(
fmt.Sprintf(`Claim owner's outstanding Hard rewards using given multiplier multiplier,
Example:
$ %s tx %s claim-hard kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw large
`, version.ClientName, types.ModuleName),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
owner, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
msg := types.NewMsgClaimHardLiquidityProviderReward(owner, args[1])
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}

View File

@ -15,11 +15,12 @@ import (
)
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/claims", types.ModuleName), queryClaimsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/cdp-claims", types.ModuleName), queryCdpClaimsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/hard-claims", types.ModuleName), queryHardClaimsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
}
func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
func queryCdpClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if err != nil {
@ -41,14 +42,54 @@ func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
}
}
queryParams := types.NewQueryClaimsParams(page, limit, owner)
queryParams := types.NewQueryCdpClaimsParams(page, limit, owner)
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
return
}
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetClaims), bz)
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetCdpClaims), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
func queryHardClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
var owner sdk.AccAddress
if x := r.URL.Query().Get(types.RestClaimOwner); len(x) != 0 {
ownerStr := strings.ToLower(strings.TrimSpace(x))
owner, err = sdk.AccAddressFromBech32(ownerStr)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from claim owner %s", ownerStr))
return
}
}
queryParams := types.NewQueryHardClaimsParams(page, limit, owner)
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
return
}
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetHardClaims), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return

View File

@ -16,10 +16,11 @@ import (
)
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/incentive/claim", postClaimHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-cdp", postClaimCdpHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/incentive/claim-hard", postClaimHardHandlerFn(cliCtx)).Methods("POST")
}
func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
func postClaimCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var requestBody types.PostClaimReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
@ -51,3 +52,36 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
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 types.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.NewMsgClaimHardLiquidityProviderReward(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})
}
}

View File

@ -15,6 +15,8 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
switch msg := msg.(type) {
case types.MsgClaimUSDXMintingReward:
return handleMsgClaimUSDXMintingReward(ctx, k, msg)
case types.MsgClaimHardLiquidityProviderReward:
return handleMsgClaimHardLiquidityProviderReward(ctx, k, msg)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
@ -23,7 +25,18 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingReward) (*sdk.Result, error) {
err := k.ClaimReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName))
err := k.ClaimUSDXMintingReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName))
if err != nil {
return nil, err
}
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
func handleMsgClaimHardLiquidityProviderReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimHardLiquidityProviderReward) (*sdk.Result, error) {
err := k.ClaimHardReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName))
if err != nil {
return nil, err
}

View File

@ -63,7 +63,34 @@ func (suite *HandlerTestSuite) SetupTest() {
suite.ctx = ctx
}
func (suite *HandlerTestSuite) addClaim() {
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) TestMsgHardLiquidityProviderClaimReward() {
suite.addHardLiquidityProviderClaim()
msg := incentive.NewMsgClaimHardLiquidityProviderReward(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())}
c1 := incentive.NewHardLiquidityProviderClaim(suite.addrs[0], c("ukava", 1000000), rewardPeriod, rewardPeriod, rewardPeriod)
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)
@ -73,13 +100,6 @@ func (suite *HandlerTestSuite) addClaim() {
})
}
func (suite *HandlerTestSuite) TestMsgClaimReward() {
suite.addClaim()
msg := incentive.NewMsgClaimUSDXMintingReward(suite.addrs[0], "small")
res, err := suite.handler(suite.ctx, msg)
suite.NoError(err)
suite.Require().NotNil(res)
}
func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}

View File

@ -12,8 +12,8 @@ import (
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
// ClaimReward sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
// 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)
@ -30,7 +30,7 @@ func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}
claim, err := k.SynchronizeClaim(ctx, claim)
claim, err := k.SynchronizeUSDXMintingClaim(ctx, claim)
if err != nil {
return err
}
@ -47,13 +47,64 @@ func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName
return err
}
k.ZeroClaim(ctx, claim)
k.ZeroUSDXMintingClaim(ctx, claim)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
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)
}
rewardAmount := claim.Reward.Amount.ToDec().Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
length := ctx.BlockTime().AddDate(0, int(multiplier.MonthsLockup), 0).Unix() - ctx.BlockTime().Unix()
err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(rewardCoin), 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

View File

@ -14,12 +14,13 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/kava-labs/kava/app"
cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/hard"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
func (suite *KeeperTestSuite) TestPayoutClaim() {
func (suite *KeeperTestSuite) TestPayoutUSDXMintingClaim() {
type args struct {
ctype string
rewardsPerSecond sdk.Coin
@ -129,7 +130,7 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
err = suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
suite.Require().NoError(err)
err = suite.keeper.ClaimReward(suite.ctx, suite.addrs[0], tc.args.multiplier)
err = suite.keeper.ClaimUSDXMintingReward(suite.ctx, suite.addrs[0], tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
@ -155,6 +156,173 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
}
}
func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() {
type args struct {
deposit sdk.Coins
borrow sdk.Coins
rewardsPerSecond sdk.Coin
initialTime time.Time
multipliers types.Multipliers
multiplier types.MultiplierName
timeElapsed int64
expectedReward sdk.Coin
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{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: 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,
expectedReward: c("hard", 21142771200), // 10571385600 (deposit reward) + 10571385600 (borrow reward)
expectedPeriods: vesting.Periods{vesting.Period{Length: 31536000, Amount: cs(c("hard", 21142771200))}},
isPeriodicVestingAccount: true,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"invalid zero rewards",
args{
deposit: cs(c("bnb", 10000000000)),
borrow: cs(c("bnb", 5000000000)),
rewardsPerSecond: 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,
expectedReward: sdk.Coin{},
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 kavadist state
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("hard", 1000000000000)))
suite.Require().NoError(err)
// Set up generic reward periods
var rewardPeriods types.RewardPeriods
for _, coin := range tc.args.deposit {
rewardPeriod := types.NewRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)
rewardPeriods = append(rewardPeriods, rewardPeriod)
}
// Set up incentive state
params := types.NewParams(
rewardPeriods, rewardPeriods, rewardPeriods, rewardPeriods,
tc.args.multipliers,
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
for _, coin := range tc.args.deposit {
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime)
suite.keeper.SetHardSupplyRewardFactor(suite.ctx, coin.Denom, sdk.ZeroDec())
}
for _, coin := range tc.args.borrow {
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime)
suite.keeper.SetHardBorrowRewardFactor(suite.ctx, coin.Denom, sdk.ZeroDec())
}
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
// User deposits
err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.deposit)
suite.Require().NoError(err)
// User borrows
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)
suite.Require().Equal(sdk.ZeroInt(), claim.Reward.Amount)
// 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.GetHardSupplyRewardPeriod(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.GetHardBorrowRewardPeriod(runCtx, coin.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
}
// Fetch pre-claim balances
ak := suite.app.GetAccountKeeper()
preClaimAcc := ak.GetAccount(runCtx, suite.addrs[3])
err = suite.keeper.ClaimHardReward(runCtx, suite.addrs[3], 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[3])
suite.Require().Equal(preClaimAcc.GetCoins().Add(tc.args.expectedReward), 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 the claim's reward amount has been reset to 0
claim, found := suite.keeper.GetHardLiquidityProviderClaim(runCtx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(c("hard", 0), claim.Reward)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
type accountArgs struct {
periods vesting.Periods

View File

@ -17,8 +17,10 @@ func NewQuerier(k Keeper) sdk.Querier {
switch path[0] {
case types.QueryGetParams:
return queryGetParams(ctx, req, k)
case types.QueryGetClaims:
return queryGetClaims(ctx, req, k)
case types.QueryGetCdpClaims:
return queryGetCdpClaims(ctx, req, k)
case types.QueryGetHardClaims:
return queryGetHardClaims(ctx, req, k)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
}
@ -38,8 +40,8 @@ func queryGetParams(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e
return bz, nil
}
func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var requestParams types.QueryClaimsParams
func queryGetCdpClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var requestParams types.QueryCdpClaimsParams
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
@ -67,3 +69,33 @@ func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e
}
return bz, nil
}
func queryGetHardClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var requestParams types.QueryHardClaimsParams
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
var claims types.HardLiquidityProviderClaims
if len(requestParams.Owner) > 0 {
claim, _ := k.GetHardLiquidityProviderClaim(ctx, requestParams.Owner)
claims = append(claims, claim)
} else {
claims = k.GetAllHardLiquidityProviderClaims(ctx)
}
var paginatedClaims types.HardLiquidityProviderClaims
start, end := client.Paginate(len(claims), requestParams.Page, requestParams.Limit, 100)
if start < 0 || end < 0 {
paginatedClaims = types.HardLiquidityProviderClaims{}
} else {
paginatedClaims = claims[start:end]
}
bz, err := codec.MarshalJSONIndent(k.cdc, paginatedClaims)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}

View File

@ -518,16 +518,16 @@ func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.Acc
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// ZeroClaim zeroes out the claim object's rewards and returns the updated claim object
func (k Keeper) ZeroClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim {
// ZeroUSDXMintingClaim zeroes out the claim object's rewards and returns the updated claim object
func (k Keeper) ZeroUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim {
claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt())
k.SetUSDXMintingClaim(ctx, claim)
return claim
}
// SynchronizeClaim updates the claim object by adding any rewards that have accumulated.
// SynchronizeUSDXMintingClaim updates the claim object by adding any rewards that have accumulated.
// Returns the updated claim object
func (k Keeper) SynchronizeClaim(ctx sdk.Context, claim types.USDXMintingClaim) (types.USDXMintingClaim, error) {
func (k Keeper) SynchronizeUSDXMintingClaim(ctx sdk.Context, claim types.USDXMintingClaim) (types.USDXMintingClaim, error) {
for _, ri := range claim.RewardIndexes {
cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.Owner, ri.CollateralType)
if !found {
@ -546,6 +546,31 @@ func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CD
return claim
}
// SynchronizeHardLiquidityProviderClaim adds any accumulated rewards
func (k Keeper) SynchronizeHardLiquidityProviderClaim(ctx sdk.Context, owner sdk.AccAddress) {
// Synchronize any hard liquidity supply-side rewards
deposit, foundDeposit := k.hardKeeper.GetDeposit(ctx, owner)
if foundDeposit {
k.SynchronizeHardSupplyReward(ctx, deposit)
}
// Synchronize any hard liquidity borrow-side rewards
borrow, foundBorrow := k.hardKeeper.GetBorrow(ctx, owner)
if foundBorrow {
k.SynchronizeHardBorrowReward(ctx, borrow)
}
// Synchronize any hard delegator rewards
k.SynchronizeHardDelegatorRewards(ctx, owner)
}
// ZeroHardLiquidityProviderClaim zeroes out the claim object's rewards and returns the updated claim object
func (k Keeper) ZeroHardLiquidityProviderClaim(ctx sdk.Context, claim types.HardLiquidityProviderClaim) types.HardLiquidityProviderClaim {
claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt())
k.SetHardLiquidityProviderClaim(ctx, claim)
return claim
}
// CalculateTimeElapsed calculates the number of reward-eligible seconds that have passed since the previous
// time rewards were accrued, taking into account the end time of the reward period
func CalculateTimeElapsed(rewardPeriod types.RewardPeriod, blockTime time.Time, previousAccrualTime time.Time) sdk.Int {

View File

@ -20,4 +20,5 @@ func RegisterCodec(cdc *codec.Codec) {
// Register msgs
cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil)
cdc.RegisterConcrete(MsgClaimHardLiquidityProviderReward{}, "incentive/MsgClaimHardLiquidityProviderReward", nil)
}

View File

@ -10,6 +10,7 @@ const (
AttributeValueCategory = ModuleName
AttributeKeyClaimedBy = "claimed_by"
AttributeKeyClaimAmount = "claim_amount"
AttributeKeyClaimType = "claim_type"
AttributeKeyRewardPeriod = "reward_period"
AttributeKeyClaimPeriod = "claim_period"
)

View File

@ -33,6 +33,8 @@ type CdpKeeper interface {
// HardKeeper defines the expected hard keeper for interacting with Hard protocol
type HardKeeper interface {
GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (hardtypes.Deposit, bool)
GetBorrow(ctx sdk.Context, borrower sdk.AccAddress) (hardtypes.Borrow, bool)
GetSupplyInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool)
GetBorrowInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool)
GetBorrowedCoins(ctx sdk.Context) (coins sdk.Coins, found bool)

View File

@ -9,8 +9,9 @@ import (
// ensure Msg interface compliance at compile time
var _ sdk.Msg = &MsgClaimUSDXMintingReward{}
var _ sdk.Msg = &MsgClaimHardLiquidityProviderReward{}
// MsgClaimUSDXMintingReward message type used to claim rewards
// MsgClaimUSDXMintingReward message type used to claim USDX minting rewards
type MsgClaimUSDXMintingReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
@ -28,7 +29,7 @@ func NewMsgClaimUSDXMintingReward(sender sdk.AccAddress, multiplierName string)
func (msg MsgClaimUSDXMintingReward) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimUSDXMintingReward) Type() string { return "claim_reward" }
func (msg MsgClaimUSDXMintingReward) Type() string { return "claim_usdx_minting_reward" }
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimUSDXMintingReward) ValidateBasic() error {
@ -48,3 +49,44 @@ func (msg MsgClaimUSDXMintingReward) GetSignBytes() []byte {
func (msg MsgClaimUSDXMintingReward) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}
// MsgClaimHardLiquidityProviderReward message type used to claim Hard liquidity provider rewards
type MsgClaimHardLiquidityProviderReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
// NewMsgClaimHardLiquidityProviderReward returns a new MsgClaimHardLiquidityProviderReward.
func NewMsgClaimHardLiquidityProviderReward(sender sdk.AccAddress, multiplierName string) MsgClaimHardLiquidityProviderReward {
return MsgClaimHardLiquidityProviderReward{
Sender: sender,
MultiplierName: multiplierName,
}
}
// Route return the message type used for routing the message.
func (msg MsgClaimHardLiquidityProviderReward) Route() string { return RouterKey }
// Type returns a human-readable string for the message, intended for utilization within tags.
func (msg MsgClaimHardLiquidityProviderReward) Type() string {
return "claim_hard_liquidity_provider_reward"
}
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgClaimHardLiquidityProviderReward) ValidateBasic() error {
if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
}
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
}
// GetSignBytes gets the canonical byte representation of the Msg.
func (msg MsgClaimHardLiquidityProviderReward) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// GetSigners returns the addresses of signers that must sign.
func (msg MsgClaimHardLiquidityProviderReward) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}

View File

@ -7,7 +7,8 @@ import (
// Querier routes for the incentive module
const (
QueryGetClaims = "claims"
QueryGetCdpClaims = "cdp-claims"
QueryGetHardClaims = "hard-claims"
RestClaimOwner = "owner"
RestClaimCollateralType = "collateral_type"
QueryGetParams = "parameters"
@ -15,16 +16,32 @@ const (
QueryGetClaimPeriods = "claim-periods"
)
// QueryClaimsParams params for query /incentive/claims
type QueryClaimsParams struct {
// QueryCdpClaimsParams params for query /incentive/claims
type QueryCdpClaimsParams struct {
Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"`
Owner sdk.AccAddress
}
// NewQueryClaimsParams returns QueryClaimsParams
func NewQueryClaimsParams(page, limit int, owner sdk.AccAddress) QueryClaimsParams {
return QueryClaimsParams{
// NewQueryCdpClaimsParams returns QueryCdpClaimsParams
func NewQueryCdpClaimsParams(page, limit int, owner sdk.AccAddress) QueryCdpClaimsParams {
return QueryCdpClaimsParams{
Page: page,
Limit: limit,
Owner: owner,
}
}
// QueryHardClaimsParams params for query /incentive/claims
type QueryHardClaimsParams struct {
Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"`
Owner sdk.AccAddress
}
// NewQueryHardClaimsParams returns QueryHardClaimsParams
func NewQueryHardClaimsParams(page, limit int, owner sdk.AccAddress) QueryHardClaimsParams {
return QueryHardClaimsParams{
Page: page,
Limit: limit,
Owner: owner,