[R4R] feat: add variable length lockups for incentive rewards (#655)

* fix: update params in spec to match implementation

* feat: add variable length lockups for incentive rewards

* fix typos

* update spec

* address review comments

* feat: improve claim test
This commit is contained in:
Kevin Davis 2020-09-21 16:20:11 -04:00 committed by GitHub
parent b1493d307c
commit 7292b8843a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 590 additions and 247 deletions

View File

@ -1,10 +1,6 @@
package incentive package incentive
// nolint // DO NOT EDIT - generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen)
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper
// ALIASGEN: github.com/kava-labs/kava/x/incentive/types
import ( import (
"github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/keeper"
@ -12,89 +8,104 @@ import (
) )
const ( const (
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount
AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
AttributeValueCategory = types.AttributeValueCategory
DefaultParamspace = types.DefaultParamspace
EventTypeClaim = types.EventTypeClaim EventTypeClaim = types.EventTypeClaim
EventTypeRewardPeriod = types.EventTypeRewardPeriod
EventTypeClaimPeriod = types.EventTypeClaimPeriod EventTypeClaimPeriod = types.EventTypeClaimPeriod
EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry
AttributeValueCategory = types.AttributeValueCategory EventTypeRewardPeriod = types.EventTypeRewardPeriod
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy Large = types.Large
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount Medium = types.Medium
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod
ModuleName = types.ModuleName ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
DefaultParamspace = types.DefaultParamspace
QuerierRoute = types.QuerierRoute QuerierRoute = types.QuerierRoute
QueryGetClaimPeriods = types.QueryGetClaimPeriods
QueryGetClaims = types.QueryGetClaims QueryGetClaims = types.QueryGetClaims
RestClaimOwner = types.RestClaimOwner
RestClaimCollateralType = types.RestClaimCollateralType
QueryGetParams = types.QueryGetParams QueryGetParams = types.QueryGetParams
QueryGetRewardPeriods = types.QueryGetRewardPeriods
RestClaimCollateralType = types.RestClaimCollateralType
RestClaimOwner = types.RestClaimOwner
RouterKey = types.RouterKey
Small = types.Small
StoreKey = types.StoreKey
) )
var ( var (
// functions aliases // function aliases
NewKeeper = keeper.NewKeeper NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier NewQuerier = keeper.NewQuerier
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
RegisterCodec = types.RegisterCodec
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
BytesToUint64 = types.BytesToUint64 BytesToUint64 = types.BytesToUint64
DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams
GetClaimPeriodPrefix = types.GetClaimPeriodPrefix GetClaimPeriodPrefix = types.GetClaimPeriodPrefix
GetClaimPrefix = types.GetClaimPrefix GetClaimPrefix = types.GetClaimPrefix
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
NewAugmentedClaim = types.NewAugmentedClaim
NewClaim = types.NewClaim
NewClaimPeriod = types.NewClaimPeriod
NewGenesisState = types.NewGenesisState
NewMsgClaimReward = types.NewMsgClaimReward NewMsgClaimReward = types.NewMsgClaimReward
NewMultiplier = types.NewMultiplier
NewParams = types.NewParams NewParams = types.NewParams
DefaultParams = types.DefaultParams
ParamKeyTable = types.ParamKeyTable
NewReward = types.NewReward
NewPeriod = types.NewPeriod NewPeriod = types.NewPeriod
NewQueryClaimsParams = types.NewQueryClaimsParams NewQueryClaimsParams = types.NewQueryClaimsParams
NewReward = types.NewReward
NewRewardPeriod = types.NewRewardPeriod NewRewardPeriod = types.NewRewardPeriod
NewClaimPeriod = types.NewClaimPeriod
NewClaim = types.NewClaim
NewRewardPeriodFromReward = types.NewRewardPeriodFromReward NewRewardPeriodFromReward = types.NewRewardPeriodFromReward
ParamKeyTable = types.ParamKeyTable
RegisterCodec = types.RegisterCodec
// variable aliases // variable aliases
ModuleCdc = types.ModuleCdc ClaimKeyPrefix = types.ClaimKeyPrefix
ClaimPeriodKeyPrefix = types.ClaimPeriodKeyPrefix
DefaultActive = types.DefaultActive
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
DefaultRewards = types.DefaultRewards
ErrAccountNotFound = types.ErrAccountNotFound
ErrClaimNotFound = types.ErrClaimNotFound ErrClaimNotFound = types.ErrClaimNotFound
ErrClaimPeriodNotFound = types.ErrClaimPeriodNotFound ErrClaimPeriodNotFound = types.ErrClaimPeriodNotFound
ErrInvalidAccountType = types.ErrInvalidAccountType
ErrNoClaimsFound = types.ErrNoClaimsFound
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
RewardPeriodKeyPrefix = types.RewardPeriodKeyPrefix ErrInvalidAccountType = types.ErrInvalidAccountType
ClaimPeriodKeyPrefix = types.ClaimPeriodKeyPrefix ErrInvalidMultiplier = types.ErrInvalidMultiplier
ClaimKeyPrefix = types.ClaimKeyPrefix ErrNoClaimsFound = types.ErrNoClaimsFound
NextClaimPeriodIDPrefix = types.NextClaimPeriodIDPrefix ErrZeroClaim = types.ErrZeroClaim
PreviousBlockTimeKey = types.PreviousBlockTimeKey GovDenom = types.GovDenom
IncentiveMacc = types.IncentiveMacc
KeyActive = types.KeyActive KeyActive = types.KeyActive
KeyRewards = types.KeyRewards KeyRewards = types.KeyRewards
DefaultActive = types.DefaultActive ModuleCdc = types.ModuleCdc
DefaultRewards = types.DefaultRewards NextClaimPeriodIDPrefix = types.NextClaimPeriodIDPrefix
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime PreviousBlockTimeKey = types.PreviousBlockTimeKey
GovDenom = types.GovDenom
PrincipalDenom = types.PrincipalDenom PrincipalDenom = types.PrincipalDenom
IncentiveMacc = types.IncentiveMacc RewardPeriodKeyPrefix = types.RewardPeriodKeyPrefix
) )
type ( type (
Keeper = keeper.Keeper Keeper = keeper.Keeper
SupplyKeeper = types.SupplyKeeper
CdpKeeper = types.CdpKeeper
AccountKeeper = types.AccountKeeper AccountKeeper = types.AccountKeeper
AugmentedClaim = types.AugmentedClaim
AugmentedClaims = types.AugmentedClaims
CdpKeeper = types.CdpKeeper
Claim = types.Claim
ClaimPeriod = types.ClaimPeriod
ClaimPeriods = types.ClaimPeriods
Claims = types.Claims
GenesisClaimPeriodID = types.GenesisClaimPeriodID GenesisClaimPeriodID = types.GenesisClaimPeriodID
GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs
GenesisState = types.GenesisState GenesisState = types.GenesisState
MsgClaimReward = types.MsgClaimReward MsgClaimReward = types.MsgClaimReward
Multiplier = types.Multiplier
MultiplierName = types.MultiplierName
Multipliers = types.Multipliers
Params = types.Params Params = types.Params
Reward = types.Reward
Rewards = types.Rewards
QueryClaimsParams = types.QueryClaimsParams
PostClaimReq = types.PostClaimReq PostClaimReq = types.PostClaimReq
QueryClaimsParams = types.QueryClaimsParams
Reward = types.Reward
RewardPeriod = types.RewardPeriod RewardPeriod = types.RewardPeriod
RewardPeriods = types.RewardPeriods RewardPeriods = types.RewardPeriods
ClaimPeriod = types.ClaimPeriod Rewards = types.Rewards
ClaimPeriods = types.ClaimPeriods SupplyKeeper = types.SupplyKeeper
Claim = types.Claim
Claims = types.Claims
) )

View File

@ -44,7 +44,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb $ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb
`, version.ClientName, types.ModuleName), `, version.ClientName, types.ModuleName),
), ),
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(3),
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)
@ -54,7 +54,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
return err return err
} }
msg := types.NewMsgClaimReward(owner, args[1]) msg := types.NewMsgClaimReward(owner, args[1], args[2])
err = msg.ValidateBasic() err = msg.ValidateBasic()
if err != nil { if err != nil {
return err return err

View File

@ -42,7 +42,7 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return return
} }
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType) msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType, 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

@ -1,6 +1,8 @@
package incentive package incentive
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"
@ -30,7 +32,7 @@ func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimRe
} }
for _, claim := range claims { for _, claim := range claims {
err := k.PayoutClaim(ctx, claim.Owner, claim.CollateralType, claim.ClaimPeriodID) err := k.PayoutClaim(ctx, claim.Owner, claim.CollateralType, claim.ClaimPeriodID, types.MultiplierName(strings.ToLower(msg.MultiplierName)))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -55,7 +55,7 @@ func (suite *HandlerTestSuite) addClaim() {
macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName) macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName)
err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000))) err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000)))
suite.Require().NoError(err) suite.Require().NoError(err)
cp := incentive.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) cp := incentive.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), incentive.Multipliers{incentive.NewMultiplier(incentive.Small, 1, sdk.MustNewDecFromStr("0.33"))})
suite.NotPanics(func() { suite.NotPanics(func() {
suite.keeper.SetClaimPeriod(suite.ctx, cp) suite.keeper.SetClaimPeriod(suite.ctx, cp)
}) })
@ -67,7 +67,7 @@ func (suite *HandlerTestSuite) addClaim() {
func (suite *HandlerTestSuite) TestMsgClaimReward() { func (suite *HandlerTestSuite) TestMsgClaimReward() {
suite.addClaim() suite.addClaim()
msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb") msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb", "small")
res, err := suite.handler(suite.ctx, msg) res, err := suite.handler(suite.ctx, msg)
suite.NoError(err) suite.NoError(err)
suite.Require().NotNil(res) suite.Require().NotNil(res)

View File

@ -52,7 +52,7 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
} }
func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() { func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() {
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
_, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") _, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb")
suite.False(found) suite.False(found)
suite.NotPanics(func() { suite.NotPanics(func() {
@ -69,7 +69,7 @@ func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() {
} }
func (suite *KeeperTestSuite) TestGetSetDeleteClaimPeriod() { func (suite *KeeperTestSuite) TestGetSetDeleteClaimPeriod() {
cp := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) cp := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
suite.False(found) suite.False(found)
suite.NotPanics(func() { suite.NotPanics(func() {
@ -149,13 +149,13 @@ func (suite *KeeperTestSuite) TestIterateMethods() {
} }
func (suite *KeeperTestSuite) addObjectsToStore() { func (suite *KeeperTestSuite) addObjectsToStore() {
rp1 := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) rp1 := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
rp2 := types.NewRewardPeriod("xrp", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) rp2 := types.NewRewardPeriod("xrp", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
suite.keeper.SetRewardPeriod(suite.ctx, rp1) suite.keeper.SetRewardPeriod(suite.ctx, rp1)
suite.keeper.SetRewardPeriod(suite.ctx, rp2) suite.keeper.SetRewardPeriod(suite.ctx, rp2)
cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
cp2 := types.NewClaimPeriod("xrp", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) cp2 := types.NewClaimPeriod("xrp", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
suite.keeper.SetClaimPeriod(suite.ctx, cp1) suite.keeper.SetClaimPeriod(suite.ctx, cp1)
suite.keeper.SetClaimPeriod(suite.ctx, cp2) suite.keeper.SetClaimPeriod(suite.ctx, cp2)
@ -168,7 +168,7 @@ func (suite *KeeperTestSuite) addObjectsToStore() {
suite.keeper.SetClaim(suite.ctx, c2) suite.keeper.SetClaim(suite.ctx, c2)
params := types.NewParams( params := types.NewParams(
true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Hour*7*24)},
) )
suite.keeper.SetParams(suite.ctx, params) suite.keeper.SetParams(suite.ctx, params)

View File

@ -15,7 +15,7 @@ import (
) )
// PayoutClaim sends the timelocked claim coins to the input address // PayoutClaim sends the timelocked claim coins to the input address
func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64) error { func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64, multiplierName types.MultiplierName) error {
claim, found := k.GetClaim(ctx, addr, collateralType, id) claim, found := k.GetClaim(ctx, addr, collateralType, id)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, collateral type %s, address: %s", id, collateralType, addr) return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, collateral type %s, address: %s", id, collateralType, addr)
@ -24,7 +24,20 @@ func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType
if !found { if !found {
return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, collateral type: %s", id, collateralType) return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, collateral type: %s", id, collateralType)
} }
err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(claim.Reward), int64(claimPeriod.TimeLock.Seconds()))
multiplier, found := claimPeriod.GetMultiplier(multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
rewardAmount := sdk.NewDecFromInt(claim.Reward.Amount).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 { if err != nil {
return err return err
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist" "github.com/kava-labs/kava/x/kavadist"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting" validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
"github.com/tendermint/tendermint/crypto"
) )
func (suite *KeeperTestSuite) setupChain() { func (suite *KeeperTestSuite) setupChain() {
@ -85,8 +86,8 @@ func (suite *KeeperTestSuite) setupExpiredClaims() {
) )
// creates two claim periods, one expired, and one that expires in the future // creates two claim periods, one expired, and one that expires in the future
cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), time.Hour*8766) cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 0), time.Hour*8766) cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 0), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
suite.keeper = tApp.GetIncentiveKeeper() suite.keeper = tApp.GetIncentiveKeeper()
suite.keeper.SetClaimPeriod(ctx, cp1) suite.keeper.SetClaimPeriod(ctx, cp1)
suite.keeper.SetClaimPeriod(ctx, cp2) suite.keeper.SetClaimPeriod(ctx, cp2)
@ -417,50 +418,6 @@ func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() {
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType)) suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
} }
func (suite *KeeperTestSuite) TestPayoutClaim() {
suite.setupChain() // adds 3 accounts - 1 periodic vesting account, 1 base account, and 1 validator vesting account
// add 2 claims that correspond to an existing claim period and one claim that has no corresponding claim period
cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766)
suite.keeper.SetClaimPeriod(suite.ctx, cp1)
// valid claim for addrs[0]
c1 := types.NewClaim(suite.addrs[0], c("ukava", 100), "bnb", 1)
// invalid claim for addrs[0]
c2 := types.NewClaim(suite.addrs[0], c("ukava", 100), "xrp", 1)
// valid claim for addrs[1]
c3 := types.NewClaim(suite.addrs[1], c("ukava", 100), "bnb", 1)
suite.keeper.SetClaim(suite.ctx, c1)
suite.keeper.SetClaim(suite.ctx, c2)
suite.keeper.SetClaim(suite.ctx, c3)
// existing claim with corresponding claim period successfully claimed by existing periodic vesting account
err := suite.keeper.PayoutClaim(suite.ctx.WithBlockTime(time.Unix(3700, 0)), suite.addrs[0], "bnb", 1)
suite.Require().NoError(err)
acc := suite.getAccount(suite.addrs[0])
// account is a periodic vesting account
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
suite.True(ok)
// vesting balance is correct
suite.Equal(cs(c("ukava", 500)), vacc.OriginalVesting)
// existing claim with corresponding claim period successfully claimed by base account
err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[1], "bnb", 1)
suite.Require().NoError(err)
acc = suite.getAccount(suite.addrs[1])
// account has become a periodic vesting account
vacc, ok = acc.(*vesting.PeriodicVestingAccount)
suite.True(ok)
// vesting balance is correct
suite.Equal(cs(c("ukava", 100)), vacc.OriginalVesting)
// addrs[3] has no claims
err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[3], "bnb", 1)
suite.Require().True(errors.Is(err, types.ErrClaimNotFound))
// addrs[0] has an xrp claim, but there is not corresponding claim period
err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[0], "xrp", 1)
suite.Require().True(errors.Is(err, types.ErrClaimPeriodNotFound))
}
func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() { func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() {
suite.setupExpiredClaims() // creates new app state with one non-expired claim period (xrp) and one expired claim period (bnb) as well as a claim that corresponds to each claim period suite.setupExpiredClaims() // creates new app state with one non-expired claim period (xrp) and one expired claim period (bnb) as well as a claim that corresponds to each claim period
@ -491,3 +448,229 @@ func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() {
suite.True(found) suite.True(found)
} }
func (suite *KeeperTestSuite) TestPayoutClaim() {
type args struct {
claimOwner sdk.AccAddress
collateralType string
id uint64
multiplier types.MultiplierName
blockTime time.Time
rewards types.Rewards
rewardperiods types.RewardPeriods
claimPeriods types.ClaimPeriods
claims types.Claims
genIDs types.GenesisClaimPeriodIDs
active bool
validatorVesting bool
expectedAccountBalance sdk.Coins
expectedModAccountBalance sdk.Coins
expectedVestingAccount bool
expectedVestingLength int64
}
type errArgs struct {
expectPass bool
contains string
}
type claimTest struct {
name string
args args
errArgs errArgs
}
testCases := []claimTest{
{
"valid small claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
collateralType: "bnb-a",
id: 1,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
active: true,
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
expectedVestingAccount: true,
expectedVestingLength: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).AddDate(0, 1, 0).Unix() - time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Unix(),
multiplier: types.Small,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid large claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
collateralType: "bnb-a",
id: 1,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
active: true,
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.Coins(nil),
expectedVestingAccount: true,
expectedVestingLength: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).AddDate(0, 12, 0).Unix() - time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Unix(),
multiplier: types.Large,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid liquid claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
collateralType: "bnb-a",
id: 1,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
active: true,
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"no matching claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
collateralType: "btcb-a",
id: 1,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
active: true,
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: false,
contains: "no claim with input id found for owner and collateral type",
},
},
{
"validator vesting claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
collateralType: "bnb-a",
id: 1,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
active: true,
validatorVesting: true,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: false,
contains: "account type not supported",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// create new app with one funded account
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
// Initialize test app and set context
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime})
authGS := app.NewAuthGenState(
[]sdk.AccAddress{tc.args.claimOwner},
[]sdk.Coins{
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
})
incentiveGS := types.NewGenesisState(types.NewParams(tc.args.active, tc.args.rewards), types.DefaultPreviousBlockTime, tc.args.rewardperiods, tc.args.claimPeriods, tc.args.claims, tc.args.genIDs)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)})
if tc.args.validatorVesting {
ak := tApp.GetAccountKeeper()
acc := ak.GetAccount(ctx, tc.args.claimOwner)
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
bva, err := vesting.NewBaseVestingAccount(
bacc,
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(20))), time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix()+100)
suite.Require().NoError(err)
vva := validatorvesting.NewValidatorVestingAccountRaw(
bva,
time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix(),
vesting.Periods{
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}},
sdk.ConsAddress(crypto.AddressHash([]byte("test"))),
sdk.AccAddress{},
95,
)
err = vva.Validate()
suite.Require().NoError(err)
ak.SetAccount(ctx, vva)
}
supplyKeeper := tApp.GetSupplyKeeper()
supplyKeeper.MintCoins(ctx, types.IncentiveMacc, sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))))
keeper := tApp.GetIncentiveKeeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
err := suite.keeper.PayoutClaim(suite.ctx, tc.args.claimOwner, tc.args.collateralType, tc.args.id, tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
acc := suite.getAccount(tc.args.claimOwner)
suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
mAcc := suite.getModuleAccount(types.IncentiveMacc)
suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
if tc.args.expectedVestingAccount {
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedVestingLength, vacc.VestingPeriods[0].Length)
} else {
suite.Require().False(ok)
}
_, f := suite.keeper.GetClaim(ctx, tc.args.claimOwner, tc.args.collateralType, tc.args.id)
suite.Require().False(f)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}

View File

@ -12,7 +12,7 @@ import (
// HandleRewardPeriodExpiry deletes expired RewardPeriods from the store and creates a ClaimPeriod in the store for each expired RewardPeriod // HandleRewardPeriodExpiry deletes expired RewardPeriods from the store and creates a ClaimPeriod in the store for each expired RewardPeriod
func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod) { func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod) {
k.CreateUniqueClaimPeriod(ctx, rp.CollateralType, rp.ClaimEnd, rp.ClaimTimeLock) k.CreateUniqueClaimPeriod(ctx, rp.CollateralType, rp.ClaimEnd, rp.ClaimMultipliers)
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
store.Delete([]byte(rp.CollateralType)) store.Delete([]byte(rp.CollateralType))
return return
@ -93,9 +93,9 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
} }
// CreateUniqueClaimPeriod creates a new claim period in the store and updates the highest claim period id // CreateUniqueClaimPeriod creates a new claim period in the store and updates the highest claim period id
func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, collateralType string, end time.Time, timeLock time.Duration) { func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, collateralType string, end time.Time, multipliers types.Multipliers) {
id := k.GetNextClaimPeriodID(ctx, collateralType) id := k.GetNextClaimPeriodID(ctx, collateralType)
claimPeriod := types.NewClaimPeriod(collateralType, id, end, timeLock) claimPeriod := types.NewClaimPeriod(collateralType, id, end, multipliers)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeClaimPeriod, types.EventTypeClaimPeriod,

View File

@ -15,7 +15,7 @@ import (
) )
func (suite *KeeperTestSuite) TestExpireRewardPeriod() { func (suite *KeeperTestSuite) TestExpireRewardPeriod() {
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
suite.keeper.SetRewardPeriod(suite.ctx, rp) suite.keeper.SetRewardPeriod(suite.ctx, rp)
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
suite.NotPanics(func() { suite.NotPanics(func() {
@ -26,7 +26,7 @@ func (suite *KeeperTestSuite) TestExpireRewardPeriod() {
} }
func (suite *KeeperTestSuite) TestAddToClaim() { func (suite *KeeperTestSuite) TestAddToClaim() {
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
suite.keeper.SetRewardPeriod(suite.ctx, rp) suite.keeper.SetRewardPeriod(suite.ctx, rp)
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp) suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp)
@ -44,7 +44,7 @@ func (suite *KeeperTestSuite) TestAddToClaim() {
} }
func (suite *KeeperTestSuite) TestCreateRewardPeriod() { func (suite *KeeperTestSuite) TestCreateRewardPeriod() {
reward := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) reward := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
suite.NotPanics(func() { suite.NotPanics(func() {
suite.keeper.CreateNewRewardPeriod(suite.ctx, reward) suite.keeper.CreateNewRewardPeriod(suite.ctx, reward)
}) })
@ -53,9 +53,9 @@ func (suite *KeeperTestSuite) TestCreateRewardPeriod() {
} }
func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() { func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() {
reward1 := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) reward1 := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
reward2 := types.NewReward(false, "xrp", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) reward2 := types.NewReward(false, "xrp", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
reward3 := types.NewReward(false, "btc", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) reward3 := types.NewReward(false, "btc", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
// add a reward period to the store for a non-active reward // add a reward period to the store for a non-active reward
suite.NotPanics(func() { suite.NotPanics(func() {
suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3) suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3)
@ -214,10 +214,10 @@ func (suite *KeeperTestSuite) setupCdpChain() {
} }
incentiveGS := types.NewGenesisState( incentiveGS := types.NewGenesisState(
types.NewParams( types.NewParams(
true, types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, true, types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
), ),
types.DefaultPreviousBlockTime, types.DefaultPreviousBlockTime,
types.RewardPeriods{types.NewRewardPeriod("bnb-a", ctx.BlockTime(), ctx.BlockTime().Add(time.Hour*7*24), c("ukava", 1000), ctx.BlockTime().Add(time.Hour*7*24*2), time.Hour*365*24)}, types.RewardPeriods{types.NewRewardPeriod("bnb-a", ctx.BlockTime(), ctx.BlockTime().Add(time.Hour*7*24), c("ukava", 1000), ctx.BlockTime().Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
types.ClaimPeriods{}, types.ClaimPeriods{},
types.Claims{}, types.Claims{},
types.GenesisClaimPeriodIDs{}) types.GenesisClaimPeriodIDs{})

View File

@ -93,7 +93,7 @@ func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedP
return nil return nil
} }
// WeightedOperations returns the all the bep3 module operations with their respective weights. // WeightedOperations returns the all the incentive module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.supplyKeeper, am.keeper) return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.supplyKeeper, am.keeper)
} }

View File

@ -27,8 +27,8 @@ func TestDecodeDistributionStore(t *testing.T) {
// Set up RewardPeriod, ClaimPeriod, Claim, and previous block time // Set up RewardPeriod, ClaimPeriod, Claim, and previous block time
rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(), rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(),
sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), time.Duration(time.Hour*2)) sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), time.Duration(time.Hour*24)) claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw") addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw")
claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1) claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1)
prevBlockTime := time.Now().Add(time.Hour * -1).UTC() prevBlockTime := time.Now().Add(time.Hour * -1).UTC()

View File

@ -67,12 +67,15 @@ func genRewards(r *rand.Rand) types.Rewards {
// total reward is in range (half max total reward, max total reward) // total reward is in range (half max total reward, max total reward)
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64())) amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount)) totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount))
// generate a random number of hours between 6-48 to use for reward's times // generate a random number of months for lockups
numbHours := simulation.RandIntBetween(r, 6, 48) numMonthsSmall := simulation.RandIntBetween(r, 0, 6)
duration := time.Duration(time.Hour * time.Duration(numbHours)) numMonthsLarge := simulation.RandIntBetween(r, 7, 12)
timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration multiplierSmall := types.NewMultiplier(types.Small, int64(numMonthsSmall), sdk.MustNewDecFromStr("0.33"))
claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration multiplierLarge := types.NewMultiplier(types.Large, int64(numMonthsLarge), sdk.MustNewDecFromStr("1.0"))
rewards[i] = types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
duration := time.Duration(time.Hour * time.Duration(simulation.RandIntBetween(r, 1, 48)))
claimDuration := time.Hour * time.Duration(simulation.RandIntBetween(r, 1, 48)) // twice as long as duration
rewards[i] = types.NewReward(active, denom, totalRewards, duration, types.Multipliers{multiplierSmall, multiplierLarge}, claimDuration)
} }
return rewards return rewards
} }
@ -90,9 +93,9 @@ func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards)
// Earlier periods have larger rewards // Earlier periods have larger rewards
amount := sdk.NewCoin("ukava", baseRewardAmount.Mul(sdk.NewInt(int64(i)))) amount := sdk.NewCoin("ukava", baseRewardAmount.Mul(sdk.NewInt(int64(i))))
claimEnd := end.Add(reward.ClaimDuration) claimEnd := end.Add(reward.ClaimDuration)
claimTimeLock := reward.TimeLock claimMultipliers := reward.ClaimMultipliers
// Create reward period and append to array // Create reward period and append to array
rewardPeriods[i] = types.NewRewardPeriod(reward.CollateralType, start, end, amount, claimEnd, claimTimeLock) rewardPeriods[i] = types.NewRewardPeriod(reward.CollateralType, start, end, amount, claimEnd, claimMultipliers)
// Update start time of next reward period // Update start time of next reward period
rewardPeriodStart = end rewardPeriodStart = end
} }
@ -110,9 +113,9 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
denomRewardPeriodsCount[denom] = numbRewardPeriods denomRewardPeriodsCount[denom] = numbRewardPeriods
// Set end and timelock from the associated reward period // Set end and timelock from the associated reward period
end := rewardPeriod.ClaimEnd end := rewardPeriod.ClaimEnd
claimTimeLock := rewardPeriod.ClaimTimeLock claimMultipliers := rewardPeriod.ClaimMultipliers
// Create the new claim period for this reward period // Create the new claim period for this reward period
claimPeriods[i] = types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock) claimPeriods[i] = types.NewClaimPeriod(denom, numbRewardPeriods, end, claimMultipliers)
} }
return claimPeriods return claimPeriods
} }

View File

@ -105,7 +105,13 @@ func SimulateMsgClaimReward(ak auth.AccountKeeper, sk types.SupplyKeeper, k keep
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address) return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address)
} }
msg := types.NewMsgClaimReward(claimer.Address, claim.CollateralType) multiplierName := types.Small
if simulation.RandIntBetween(r, 0, 1) == 0 {
multiplierName = types.Large
}
msg := types.NewMsgClaimReward(claimer.Address, claim.CollateralType, string(multiplierName))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},

View File

@ -6,4 +6,4 @@ order: 1
This module presents an implementation of user incentives that are controlled by governance. When users take a certain action, in this case opening a CDP, they become eligible for rewards. Rewards are __opt in__ meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf). This module presents an implementation of user incentives that are controlled by governance. When users take a certain action, in this case opening a CDP, they become eligible for rewards. Rewards are __opt in__ meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf).
When governance adds a collateral type to be eligible for rewards, they set the rate (coins/time) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `Claim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends. When governance adds a collateral type to be eligible for rewards, they set the rate (coins/time) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `Claim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends. In addition to vesting, rewards can have multipliers that vary the number of tokens received. For example, a reward with a vesting period of 1 month may have a multiplier of 0.25, meaning that the user will receive 25% of the reward balance if they choose that vesting schedule.

View File

@ -15,13 +15,14 @@ type Params struct {
Rewards Rewards `json:"rewards" yaml:"rewards"` Rewards Rewards `json:"rewards" yaml:"rewards"`
} }
// Reward stores the specified state for a single reward period.
// Reward stores the specified state for a single reward period. // Reward stores the specified state for a single reward period.
type Reward struct { type Reward struct {
Active bool `json:"active" yaml:"active"` // governance switch to disable a period Active bool `json:"active" yaml:"active"` // governance switch to disable a period
Denom string `json:"denom" yaml:"denom"` // the collateral denom rewards apply to, must be found in the cdp collaterals CollateralType string `json:"collateral_type" yaml:"collateral_type"` // the collateral type rewards apply to, must be found in the cdp collaterals
AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` // the reward multiplier and timelock schedule - applied at the time users claim rewards
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
} }
``` ```

View File

@ -10,11 +10,13 @@ Users claim rewards using a `MsgClaimReward`.
// MsgClaimReward message type used to claim rewards // MsgClaimReward message type used to claim rewards
type MsgClaimReward struct { type MsgClaimReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Denom string `json:"denom" yaml:"denom"` CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
} }
``` ```
## State Modifications ## State Modifications
* Accumulated rewards for active claims are transferred from the `kavadist` module account to the users account as vesting coins * Accumulated rewards for active claims are transferred from the `kavadist` module account to the users account as vesting coins
* The number of coins transferred is determined by the multiplier in the message. For example, the multiplier equals 1.0, 100% of the claim's reward value is transferred. If the multiplier equals 0.5, 50% of the claim's reward value is transferred.
* The corresponding claim object(s) are deleted from the store * The corresponding claim object(s) are deleted from the store

View File

@ -14,10 +14,18 @@ The incentive module contains the following parameters:
Each `Reward` has the following parameters Each `Reward` has the following parameters
| Key | Type | Example | Description | | Key | Type | Example | Description |
|------------------|--------------------|------------------------------------|----------------------------------------------------------------| |------------------|--------------------|------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| Active | bool | "true | boolean for if rewards for this collateral are active | | Active | bool | "true | boolean for if rewards for this collateral are active |
| Denom | string | "bnb" | the collateral for which rewards are eligible | | Denom | string | "bnb" | the collateral for which rewards are eligible |
| AvailableRewards | object (coin) | `{"denom":"kava","amount":"1000"}` | the rewards available per reward period | | AvailableRewards | object (coin) | `{"denom":"kava","amount":"1000"}` | the rewards available per reward period |
| Duration | string (time ns) | "172800000000000" | the duration of each reward period | | Duration | string (time ns) | "172800000000000" | the duration of each reward period |
| TimeLock | string (time ns) | "172800000000000" | the duration for which claimed rewards will be vesting | | ClaimMultipliers | array (Multiplier) | [{see below}] | the number of months for which claimed rewards will be vesting and the multiplier applied when rewards are claimed|
| ClaimDuration | string (time ns) | "172800000000000" | how long users have to claim rewards before they expire | | ClaimDuration | string (time ns) | "172800000000000" | how long users have to claim rewards before they expire |
Each `Multiplier` has the following parameters:
| Key | Type | Example | Description |
|-----------------------|--------------------|--------------------------|-----------------------------------------------------------------|
| Name | string | "large" | the unique name of the reward multiplier |
| MonthsLockup | int | "6" | number of months HARD tokens with this multiplier are locked |
| Factor | Dec | "0.5" | the scaling factor for HARD tokens claimed with this multiplier |

View File

@ -6,11 +6,14 @@ import (
// DONTCOVER // DONTCOVER
// Incentive module errors
var ( var (
ErrClaimNotFound = sdkerrors.Register(ModuleName, 1, "no claim with input id found for owner and collateral type") ErrClaimNotFound = sdkerrors.Register(ModuleName, 2, "no claim with input id found for owner and collateral type")
ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 2, "no claim period found for id and collateral type") ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 3, "no claim period found for id and collateral type")
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 3, "account type not supported") ErrInvalidAccountType = sdkerrors.Register(ModuleName, 4, "account type not supported")
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 4, "no claims with collateral type found for address") ErrNoClaimsFound = sdkerrors.Register(ModuleName, 5, "no claims with collateral type found for address")
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 5, "module account has insufficient balance to pay claim") ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 6, "module account has insufficient balance to pay claim")
ErrAccountNotFound = sdkerrors.Register(ModuleName, 6, "account not found") ErrAccountNotFound = sdkerrors.Register(ModuleName, 7, "account not found")
ErrInvalidMultiplier = sdkerrors.Register(ModuleName, 8, "invalid rewards multiplier")
ErrZeroClaim = sdkerrors.Register(ModuleName, 9, "cannot claim - claim amount rounds to zero")
) )

View File

@ -68,11 +68,11 @@ func TestGenesisStateValidate(t *testing.T) {
rewards := Rewards{ rewards := Rewards{
NewReward( NewReward(
true, "bnb", sdk.NewCoin("ukava", sdk.NewInt(10000000000)), true, "bnb", sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
time.Hour*24*7, time.Hour*8766, time.Hour*24*14, time.Hour*24*7, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Hour*24*14,
), ),
} }
rewardPeriods := RewardPeriods{NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10)} rewardPeriods := RewardPeriods{NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))})}
claimPeriods := ClaimPeriods{NewClaimPeriod("bnb", 10, now, 100)} claimPeriods := ClaimPeriods{NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))})}
claims := Claims{NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10)} claims := Claims{NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10)}
gcps := GenesisClaimPeriodIDs{{CollateralType: "bnb", ID: 1}} gcps := GenesisClaimPeriodIDs{{CollateralType: "bnb", ID: 1}}

View File

@ -15,13 +15,15 @@ var _ sdk.Msg = &MsgClaimReward{}
type MsgClaimReward struct { type MsgClaimReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"` CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
} }
// NewMsgClaimReward returns a new MsgClaimReward. // NewMsgClaimReward returns a new MsgClaimReward.
func NewMsgClaimReward(sender sdk.AccAddress, collateralType string) MsgClaimReward { func NewMsgClaimReward(sender sdk.AccAddress, collateralType, multiplierName string) MsgClaimReward {
return MsgClaimReward{ return MsgClaimReward{
Sender: sender, Sender: sender,
CollateralType: collateralType, CollateralType: collateralType,
MultiplierName: multiplierName,
} }
} }
@ -39,7 +41,7 @@ func (msg MsgClaimReward) ValidateBasic() error {
if strings.TrimSpace(msg.CollateralType) == "" { if strings.TrimSpace(msg.CollateralType) == "" {
return fmt.Errorf("collateral type cannot be blank") return fmt.Errorf("collateral type cannot be blank")
} }
return nil return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
} }
// GetSignBytes gets the canonical byte representation of the Msg. // GetSignBytes gets the canonical byte representation of the Msg.

View File

@ -15,6 +15,7 @@ import (
type msgTest struct { type msgTest struct {
from sdk.AccAddress from sdk.AccAddress
collateralType string collateralType string
multiplierName string
expectPass bool expectPass bool
} }
@ -29,16 +30,25 @@ func (suite *MsgTestSuite) SetupTest() {
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
collateralType: "bnb", collateralType: "bnb",
multiplierName: "large",
expectPass: true, expectPass: true,
}, },
{ {
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
collateralType: "", collateralType: "",
multiplierName: "small",
expectPass: false, expectPass: false,
}, },
{ {
from: sdk.AccAddress{}, from: sdk.AccAddress{},
collateralType: "bnb", collateralType: "bnb",
multiplierName: "medium",
expectPass: false,
},
{
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
collateralType: "bnb",
multiplierName: "huge",
expectPass: false, expectPass: false,
}, },
} }
@ -47,7 +57,7 @@ func (suite *MsgTestSuite) SetupTest() {
func (suite *MsgTestSuite) TestMsgValidation() { func (suite *MsgTestSuite) TestMsgValidation() {
for _, t := range suite.tests { for _, t := range suite.tests {
msg := types.NewMsgClaimReward(t.from, t.collateralType) msg := types.NewMsgClaimReward(t.from, t.collateralType, t.multiplierName)
err := msg.ValidateBasic() err := msg.ValidateBasic()
if t.expectPass { if t.expectPass {
suite.Require().NoError(err) suite.Require().NoError(err)

View File

@ -14,6 +14,13 @@ import (
kavadistTypes "github.com/kava-labs/kava/x/kavadist/types" kavadistTypes "github.com/kava-labs/kava/x/kavadist/types"
) )
// Valid reward multipliers
const (
Small MultiplierName = "small"
Medium MultiplierName = "medium"
Large MultiplierName = "large"
)
// Parameter keys and default values // Parameter keys and default values
var ( var (
KeyActive = []byte("Active") KeyActive = []byte("Active")
@ -40,7 +47,7 @@ func NewParams(active bool, rewards Rewards) Params {
} }
} }
// DefaultParams returns default params for kavadist module // DefaultParams returns default params for incentive module
func DefaultParams() Params { func DefaultParams() Params {
return NewParams(DefaultActive, DefaultRewards) return NewParams(DefaultActive, DefaultRewards)
} }
@ -97,18 +104,18 @@ type Reward struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"` // the collateral type rewards apply to, must be found in the cdp collaterals CollateralType string `json:"collateral_type" yaml:"collateral_type"` // the collateral type rewards apply to, must be found in the cdp collaterals
AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` // the reward multiplier and timelock schedule - applied at the time users claim rewards
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
} }
// NewReward returns a new Reward // NewReward returns a new Reward
func NewReward(active bool, collateralType string, reward sdk.Coin, duration time.Duration, timelock time.Duration, claimDuration time.Duration) Reward { func NewReward(active bool, collateralType string, reward sdk.Coin, duration time.Duration, multiplier Multipliers, claimDuration time.Duration) Reward {
return Reward{ return Reward{
Active: active, Active: active,
CollateralType: collateralType, CollateralType: collateralType,
AvailableRewards: reward, AvailableRewards: reward,
Duration: duration, Duration: duration,
TimeLock: timelock, ClaimMultipliers: multiplier,
ClaimDuration: claimDuration, ClaimDuration: claimDuration,
} }
} }
@ -120,9 +127,9 @@ func (r Reward) String() string {
CollateralType: %s, CollateralType: %s,
Available Rewards: %s, Available Rewards: %s,
Duration: %s, Duration: %s,
Time Lock: %s, %s,
Claim Duration: %s`, Claim Duration: %s`,
r.Active, r.CollateralType, r.AvailableRewards, r.Duration, r.TimeLock, r.ClaimDuration) r.Active, r.CollateralType, r.AvailableRewards, r.Duration, r.ClaimMultipliers, r.ClaimDuration)
} }
// Validate performs a basic check of a reward fields. // Validate performs a basic check of a reward fields.
@ -136,8 +143,8 @@ func (r Reward) Validate() error {
if r.Duration <= 0 { if r.Duration <= 0 {
return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.CollateralType) return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.CollateralType)
} }
if r.TimeLock < 0 { if err := r.ClaimMultipliers.Validate(); err != nil {
return fmt.Errorf("reward timelock must be non-negative, is %s for %s", r.TimeLock, r.CollateralType) return err
} }
if r.ClaimDuration <= 0 { if r.ClaimDuration <= 0 {
return fmt.Errorf("claim duration must be positive, is %s for %s", r.ClaimDuration, r.CollateralType) return fmt.Errorf("claim duration must be positive, is %s for %s", r.ClaimDuration, r.CollateralType)
@ -177,3 +184,77 @@ func (rs Rewards) String() string {
} }
return out return out
} }
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
type Multiplier struct {
Name MultiplierName `json:"name" yaml:"name"`
MonthsLockup int64 `json:"months_lockup" yaml:"months_lockup"`
Factor sdk.Dec `json:"factor" yaml:"factor"`
}
// NewMultiplier returns a new Multiplier
func NewMultiplier(name MultiplierName, lockup int64, factor sdk.Dec) Multiplier {
return Multiplier{
Name: name,
MonthsLockup: lockup,
Factor: factor,
}
}
// Validate multiplier param
func (m Multiplier) Validate() error {
if err := m.Name.IsValid(); err != nil {
return err
}
if m.MonthsLockup < 0 {
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
}
if m.Factor.IsNegative() {
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
}
return nil
}
// String implements fmt.Stringer
func (m Multiplier) String() string {
return fmt.Sprintf(`Claim Multiplier:
Name: %s
Months Lockup %d
Factor %s
`, m.Name, m.MonthsLockup, m.Factor)
}
// Multipliers slice of Multiplier
type Multipliers []Multiplier
// Validate validates each multiplier
func (ms Multipliers) Validate() error {
for _, m := range ms {
if err := m.Validate(); err != nil {
return err
}
}
return nil
}
// String implements fmt.Stringer
func (ms Multipliers) String() string {
out := "Claim Multipliers\n"
for _, s := range ms {
out += fmt.Sprintf("%s\n", s)
}
return out
}
// MultiplierName name for valid multiplier
type MultiplierName string
// IsValid checks if the input is one of the expected strings
func (mn MultiplierName) IsValid() error {
switch mn {
case Small, Medium, Large:
return nil
}
return fmt.Errorf("invalid multiplier name: %s", mn)
}

View File

@ -41,7 +41,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
@ -61,7 +61,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
@ -81,7 +81,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
types.Reward{ types.Reward{
@ -89,7 +89,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
@ -109,7 +109,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * -24 * 7, Duration: time.Hour * -24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
@ -129,14 +129,14 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * -8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, -1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
}, },
errResult: errResult{ errResult: errResult{
expectPass: false, expectPass: false,
contains: "reward timelock must be non-negative", contains: "expected non-negative lockup",
}, },
}, },
{ {
@ -149,7 +149,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 0, ClaimDuration: time.Hour * 0,
}, },
}, },
@ -169,7 +169,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a", CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },
@ -189,7 +189,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "", CollateralType: "",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)), AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)),
Duration: time.Hour * 24 * 7, Duration: time.Hour * 24 * 7,
TimeLock: time.Hour * 8766, ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
ClaimDuration: time.Hour * 24 * 14, ClaimDuration: time.Hour * 24 * 14,
}, },
}, },

View File

@ -34,4 +34,5 @@ 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"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"` CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
} }

View File

@ -16,7 +16,7 @@ type RewardPeriod struct {
End time.Time `json:"end" yaml:"end"` End time.Time `json:"end" yaml:"end"`
Reward sdk.Coin `json:"reward" yaml:"reward"` // per second reward payouts Reward sdk.Coin `json:"reward" yaml:"reward"` // per second reward payouts
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
ClaimTimeLock time.Duration `json:"claim_time_lock" yaml:"claim_time_lock"` // the amount of time rewards are timelocked once they are sent to users ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` // the reward multiplier and timelock schedule - applied at the time users claim rewards
} }
// String implements fmt.Stringer // String implements fmt.Stringer
@ -27,19 +27,19 @@ func (rp RewardPeriod) String() string {
End: %s, End: %s,
Reward: %s, Reward: %s,
Claim End: %s, Claim End: %s,
Claim Time Lock: %s %s
`, rp.CollateralType, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock) `, rp.CollateralType, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimMultipliers)
} }
// NewRewardPeriod returns a new RewardPeriod // NewRewardPeriod returns a new RewardPeriod
func NewRewardPeriod(collateralType string, start time.Time, end time.Time, reward sdk.Coin, claimEnd time.Time, claimTimeLock time.Duration) RewardPeriod { func NewRewardPeriod(collateralType string, start time.Time, end time.Time, reward sdk.Coin, claimEnd time.Time, claimMultipliers Multipliers) RewardPeriod {
return RewardPeriod{ return RewardPeriod{
CollateralType: collateralType, CollateralType: collateralType,
Start: start, Start: start,
End: end, End: end,
Reward: reward, Reward: reward,
ClaimEnd: claimEnd, ClaimEnd: claimEnd,
ClaimTimeLock: claimTimeLock, ClaimMultipliers: claimMultipliers,
} }
} }
@ -60,8 +60,8 @@ func (rp RewardPeriod) Validate() error {
if rp.ClaimEnd.IsZero() { if rp.ClaimEnd.IsZero() {
return errors.New("reward period claim end time cannot be 0") return errors.New("reward period claim end time cannot be 0")
} }
if rp.ClaimTimeLock == 0 { if err := rp.ClaimMultipliers.Validate(); err != nil {
return errors.New("reward claim time lock cannot be 0") return err
} }
if strings.TrimSpace(rp.CollateralType) == "" { if strings.TrimSpace(rp.CollateralType) == "" {
return fmt.Errorf("reward period collateral type cannot be blank: %s", rp) return fmt.Errorf("reward period collateral type cannot be blank: %s", rp)
@ -95,16 +95,16 @@ type ClaimPeriod struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"` CollateralType string `json:"collateral_type" yaml:"collateral_type"`
ID uint64 `json:"id" yaml:"id"` ID uint64 `json:"id" yaml:"id"`
End time.Time `json:"end" yaml:"end"` End time.Time `json:"end" yaml:"end"`
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
} }
// NewClaimPeriod returns a new ClaimPeriod // NewClaimPeriod returns a new ClaimPeriod
func NewClaimPeriod(collateralType string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod { func NewClaimPeriod(collateralType string, id uint64, end time.Time, multipliers Multipliers) ClaimPeriod {
return ClaimPeriod{ return ClaimPeriod{
CollateralType: collateralType, CollateralType: collateralType,
ID: id, ID: id,
End: end, End: end,
TimeLock: timeLock, ClaimMultipliers: multipliers,
} }
} }
@ -116,8 +116,8 @@ func (cp ClaimPeriod) Validate() error {
if cp.End.IsZero() { if cp.End.IsZero() {
return errors.New("claim period end time cannot be 0") return errors.New("claim period end time cannot be 0")
} }
if cp.TimeLock == 0 { if err := cp.ClaimMultipliers.Validate(); err != nil {
return errors.New("claim period time lock cannot be 0") return err
} }
if strings.TrimSpace(cp.CollateralType) == "" { if strings.TrimSpace(cp.CollateralType) == "" {
return fmt.Errorf("claim period collateral type cannot be blank: %s", cp) return fmt.Errorf("claim period collateral type cannot be blank: %s", cp)
@ -131,8 +131,18 @@ func (cp ClaimPeriod) String() string {
Collateral Type: %s, Collateral Type: %s,
ID: %d, ID: %d,
End: %s, End: %s,
Claim Time Lock: %s %s
`, cp.CollateralType, cp.ID, cp.End, cp.TimeLock) `, cp.CollateralType, cp.ID, cp.End, cp.ClaimMultipliers)
}
// GetMultiplier returns the named multiplier from the input claim period
func (cp ClaimPeriod) GetMultiplier(name MultiplierName) (Multiplier, bool) {
for _, multiplier := range cp.ClaimMultipliers {
if multiplier.Name == name {
return multiplier, true
}
}
return Multiplier{}, false
} }
// ClaimPeriods array of ClaimPeriod // ClaimPeriods array of ClaimPeriod
@ -266,6 +276,6 @@ func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod
End: blockTime.Add(reward.Duration), End: blockTime.Add(reward.Duration),
Reward: rewardCoinPerSecond, Reward: rewardCoinPerSecond,
ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration), ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration),
ClaimTimeLock: reward.TimeLock, ClaimMultipliers: reward.ClaimMultipliers,
} }
} }

View File

@ -21,7 +21,7 @@ func TestRewardPeriodsValidate(t *testing.T) {
{ {
"valid", "valid",
RewardPeriods{ RewardPeriods{
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10), NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}),
}, },
true, true,
}, },
@ -80,14 +80,14 @@ func TestRewardPeriodsValidate(t *testing.T) {
false, false,
}, },
{ {
"zero claim time lock", "negative time lock",
RewardPeriods{ RewardPeriods{
{ {
Start: now, Start: now,
End: now.Add(time.Hour), End: now.Add(time.Hour),
Reward: sdk.NewCoin("bnb", sdk.OneInt()), Reward: sdk.NewCoin("bnb", sdk.OneInt()),
ClaimEnd: now, ClaimEnd: now,
ClaimTimeLock: 0, ClaimMultipliers: Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))},
}, },
}, },
false, false,
@ -100,7 +100,7 @@ func TestRewardPeriodsValidate(t *testing.T) {
End: now.Add(time.Hour), End: now.Add(time.Hour),
Reward: sdk.NewCoin("bnb", sdk.OneInt()), Reward: sdk.NewCoin("bnb", sdk.OneInt()),
ClaimEnd: now, ClaimEnd: now,
ClaimTimeLock: 10, ClaimMultipliers: Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))},
CollateralType: "", CollateralType: "",
}, },
}, },
@ -109,8 +109,8 @@ func TestRewardPeriodsValidate(t *testing.T) {
{ {
"duplicate reward period", "duplicate reward period",
RewardPeriods{ RewardPeriods{
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10), NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}),
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10), NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}),
}, },
false, false,
}, },
@ -137,7 +137,7 @@ func TestClaimPeriodsValidate(t *testing.T) {
{ {
"valid", "valid",
ClaimPeriods{ ClaimPeriods{
NewClaimPeriod("bnb", 10, now, 100), NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}),
}, },
true, true,
}, },
@ -156,31 +156,38 @@ func TestClaimPeriodsValidate(t *testing.T) {
false, false,
}, },
{ {
"zero time lock", "negative time lock",
ClaimPeriods{ ClaimPeriods{
{ID: 10, End: now, TimeLock: 0}, {ID: 10, End: now, ClaimMultipliers: Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))}},
},
false,
},
{
"negative multiplier",
ClaimPeriods{
NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("-0.33"))}),
}, },
false, false,
}, },
{ {
"start time > end time", "start time > end time",
ClaimPeriods{ ClaimPeriods{
{ID: 10, End: now, TimeLock: 0}, {ID: 10, End: now, ClaimMultipliers: Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))}},
}, },
false, false,
}, },
{ {
"invalid collateral type", "invalid collateral type",
ClaimPeriods{ ClaimPeriods{
{ID: 10, End: now, TimeLock: 100, CollateralType: ""}, {ID: 10, End: now, ClaimMultipliers: Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))}, CollateralType: ""},
}, },
false, false,
}, },
{ {
"duplicate reward period", "duplicate reward period",
ClaimPeriods{ ClaimPeriods{
NewClaimPeriod("bnb", 10, now, 100), NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))}),
NewClaimPeriod("bnb", 10, now, 100), NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, -1, sdk.MustNewDecFromStr("0.33"))}),
}, },
false, false,
}, },