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

View File

@ -44,7 +44,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb
`, version.ClientName, types.ModuleName),
),
Args: cobra.ExactArgs(2),
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
@ -54,7 +54,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
return err
}
msg := types.NewMsgClaimReward(owner, args[1])
msg := types.NewMsgClaimReward(owner, args[1], args[2])
err = msg.ValidateBasic()
if err != nil {
return err

View File

@ -42,7 +42,7 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return
}
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType)
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType, requestBody.MultiplierName)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return

View File

@ -1,6 +1,8 @@
package incentive
import (
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
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 {
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 {
return nil, err
}

View File

@ -55,7 +55,7 @@ func (suite *HandlerTestSuite) addClaim() {
macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName)
err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000)))
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.keeper.SetClaimPeriod(suite.ctx, cp)
})
@ -67,7 +67,7 @@ func (suite *HandlerTestSuite) addClaim() {
func (suite *HandlerTestSuite) TestMsgClaimReward() {
suite.addClaim()
msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb")
msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb", "small")
res, err := suite.handler(suite.ctx, msg)
suite.NoError(err)
suite.Require().NotNil(res)

View File

@ -52,7 +52,7 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
}
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")
suite.False(found)
suite.NotPanics(func() {
@ -69,7 +69,7 @@ func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() {
}
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")
suite.False(found)
suite.NotPanics(func() {
@ -149,13 +149,13 @@ func (suite *KeeperTestSuite) TestIterateMethods() {
}
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)
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)
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), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
suite.keeper.SetRewardPeriod(suite.ctx, rp1)
suite.keeper.SetRewardPeriod(suite.ctx, rp2)
cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766)
cp2 := types.NewClaimPeriod("xrp", 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), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
suite.keeper.SetClaimPeriod(suite.ctx, cp1)
suite.keeper.SetClaimPeriod(suite.ctx, cp2)
@ -168,7 +168,7 @@ func (suite *KeeperTestSuite) addObjectsToStore() {
suite.keeper.SetClaim(suite.ctx, c2)
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)

View File

@ -15,7 +15,7 @@ import (
)
// 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)
if !found {
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 {
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 {
return err
}

View File

@ -16,6 +16,7 @@ import (
"github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
"github.com/tendermint/tendermint/crypto"
)
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
cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), time.Hour*8766)
cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 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), 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.SetClaimPeriod(ctx, cp1)
suite.keeper.SetClaimPeriod(ctx, cp2)
@ -417,50 +418,6 @@ func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() {
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() {
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)
}
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
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.Delete([]byte(rp.CollateralType))
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
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)
claimPeriod := types.NewClaimPeriod(collateralType, id, end, timeLock)
claimPeriod := types.NewClaimPeriod(collateralType, id, end, multipliers)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimPeriod,

View File

@ -15,7 +15,7 @@ import (
)
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.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
suite.NotPanics(func() {
@ -26,7 +26,7 @@ func (suite *KeeperTestSuite) TestExpireRewardPeriod() {
}
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.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp)
@ -44,7 +44,7 @@ func (suite *KeeperTestSuite) TestAddToClaim() {
}
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.keeper.CreateNewRewardPeriod(suite.ctx, reward)
})
@ -53,9 +53,9 @@ func (suite *KeeperTestSuite) TestCreateRewardPeriod() {
}
func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() {
reward1 := types.NewReward(true, "bnb", 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, time.Hour*24*365, time.Hour*7*24)
reward3 := types.NewReward(false, "btc", 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, 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, 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
suite.NotPanics(func() {
suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3)
@ -214,10 +214,10 @@ func (suite *KeeperTestSuite) setupCdpChain() {
}
incentiveGS := types.NewGenesisState(
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.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.Claims{},
types.GenesisClaimPeriodIDs{})

View File

@ -93,7 +93,7 @@ func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedP
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 {
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
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))
claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), time.Duration(time.Hour*24))
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(), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw")
claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1)
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)
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount))
// generate a random number of hours between 6-48 to use for reward's times
numbHours := simulation.RandIntBetween(r, 6, 48)
duration := time.Duration(time.Hour * time.Duration(numbHours))
timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration
claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration
rewards[i] = types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
// generate a random number of months for lockups
numMonthsSmall := simulation.RandIntBetween(r, 0, 6)
numMonthsLarge := simulation.RandIntBetween(r, 7, 12)
multiplierSmall := types.NewMultiplier(types.Small, int64(numMonthsSmall), sdk.MustNewDecFromStr("0.33"))
multiplierLarge := types.NewMultiplier(types.Large, int64(numMonthsLarge), sdk.MustNewDecFromStr("1.0"))
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
}
@ -90,9 +93,9 @@ func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards)
// Earlier periods have larger rewards
amount := sdk.NewCoin("ukava", baseRewardAmount.Mul(sdk.NewInt(int64(i))))
claimEnd := end.Add(reward.ClaimDuration)
claimTimeLock := reward.TimeLock
claimMultipliers := reward.ClaimMultipliers
// 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
rewardPeriodStart = end
}
@ -110,9 +113,9 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
denomRewardPeriodsCount[denom] = numbRewardPeriods
// Set end and timelock from the associated reward period
end := rewardPeriod.ClaimEnd
claimTimeLock := rewardPeriod.ClaimTimeLock
claimMultipliers := rewardPeriod.ClaimMultipliers
// 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
}

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)
}
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(
[]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).
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

@ -11,18 +11,19 @@ order: 2
```go
// Params governance parameters for the incentive module
type Params struct {
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards
Rewards Rewards `json:"rewards" yaml:"rewards"`
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all 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.
type Reward struct {
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
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
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
Active bool `json:"active" yaml:"active"` // governance switch to disable a period
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
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
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
}
```
@ -31,12 +32,12 @@ type Reward struct {
```go
// GenesisState is the state that must be provided at genesis.
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"`
Claims Claims `json:"claims" yaml:"claims"`
NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"`
Params Params `json:"params" yaml:"params"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"`
Claims Claims `json:"claims" yaml:"claims"`
NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"`
}
```

View File

@ -9,12 +9,14 @@ Users claim rewards using a `MsgClaimReward`.
```go
// MsgClaimReward message type used to claim rewards
type MsgClaimReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Denom string `json:"denom" yaml:"denom"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
```
## State Modifications
* 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

View File

@ -13,11 +13,19 @@ The incentive module contains the following parameters:
Each `Reward` has the following parameters
| Key | Type | Example | Description |
|------------------|--------------------|------------------------------------|----------------------------------------------------------------|
| Active | bool | "true | boolean for if rewards for this collateral are active |
| Denom | string | "bnb" | the collateral for which rewards are eligible |
| AvailableRewards | object (coin) | `{"denom":"kava","amount":"1000"}` | the rewards available per 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 |
| ClaimDuration | string (time ns) | "172800000000000" | how long users have to claim rewards before they expire |
| Key | Type | Example | Description |
|------------------|--------------------|------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| Active | bool | "true | boolean for if rewards for this collateral are active |
| Denom | string | "bnb" | the collateral for which rewards are eligible |
| AvailableRewards | object (coin) | `{"denom":"kava","amount":"1000"}` | the rewards available per reward period |
| Duration | string (time ns) | "172800000000000" | the duration of each reward period |
| 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 |
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
// Incentive module errors
var (
ErrClaimNotFound = sdkerrors.Register(ModuleName, 1, "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")
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 3, "account type not supported")
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 4, "no claims with collateral type found for address")
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 5, "module account has insufficient balance to pay claim")
ErrAccountNotFound = sdkerrors.Register(ModuleName, 6, "account not found")
ErrClaimNotFound = sdkerrors.Register(ModuleName, 2, "no claim with input id found for owner and collateral type")
ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 3, "no claim period found for id and collateral type")
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 4, "account type not supported")
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 5, "no claims with collateral type found for address")
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 6, "module account has insufficient balance to pay claim")
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{
NewReward(
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)}
claimPeriods := ClaimPeriods{NewClaimPeriod("bnb", 10, now, 100)}
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, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))})}
claims := Claims{NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10)}
gcps := GenesisClaimPeriodIDs{{CollateralType: "bnb", ID: 1}}

View File

@ -15,13 +15,15 @@ var _ sdk.Msg = &MsgClaimReward{}
type MsgClaimReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}
// NewMsgClaimReward returns a new MsgClaimReward.
func NewMsgClaimReward(sender sdk.AccAddress, collateralType string) MsgClaimReward {
func NewMsgClaimReward(sender sdk.AccAddress, collateralType, multiplierName string) MsgClaimReward {
return MsgClaimReward{
Sender: sender,
CollateralType: collateralType,
MultiplierName: multiplierName,
}
}
@ -39,7 +41,7 @@ func (msg MsgClaimReward) ValidateBasic() error {
if strings.TrimSpace(msg.CollateralType) == "" {
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.

View File

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

View File

@ -14,6 +14,13 @@ import (
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
var (
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 {
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
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
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
}
// 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{
Active: active,
CollateralType: collateralType,
AvailableRewards: reward,
Duration: duration,
TimeLock: timelock,
ClaimMultipliers: multiplier,
ClaimDuration: claimDuration,
}
}
@ -120,9 +127,9 @@ func (r Reward) String() string {
CollateralType: %s,
Available Rewards: %s,
Duration: %s,
Time Lock: %s,
%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.
@ -136,8 +143,8 @@ func (r Reward) Validate() error {
if r.Duration <= 0 {
return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.CollateralType)
}
if r.TimeLock < 0 {
return fmt.Errorf("reward timelock must be non-negative, is %s for %s", r.TimeLock, r.CollateralType)
if err := r.ClaimMultipliers.Validate(); err != nil {
return err
}
if r.ClaimDuration <= 0 {
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
}
// 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",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
@ -61,7 +61,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
@ -81,7 +81,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
types.Reward{
@ -89,7 +89,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
@ -109,7 +109,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
@ -129,14 +129,14 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
},
errResult: errResult{
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",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
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,
},
},
@ -169,7 +169,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "bnb-a",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
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,
},
},
@ -189,7 +189,7 @@ func (suite *ParamTestSuite) SetupTest() {
CollateralType: "",
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)),
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,
},
},

View File

@ -34,4 +34,5 @@ type PostClaimReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
}

View File

@ -11,12 +11,12 @@ import (
// RewardPeriod stores the state of an ongoing reward
type RewardPeriod struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
Start time.Time `json:"start" yaml:"start"`
End time.Time `json:"end" yaml:"end"`
Reward sdk.Coin `json:"reward" yaml:"reward"` // per second reward payouts
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
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
Start time.Time `json:"start" yaml:"start"`
End time.Time `json:"end" yaml:"end"`
Reward sdk.Coin `json:"reward" yaml:"reward"` // per second reward payouts
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
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
@ -27,19 +27,19 @@ func (rp RewardPeriod) String() string {
End: %s,
Reward: %s,
Claim End: %s,
Claim Time Lock: %s
`, rp.CollateralType, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
%s
`, rp.CollateralType, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimMultipliers)
}
// 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{
CollateralType: collateralType,
Start: start,
End: end,
Reward: reward,
ClaimEnd: claimEnd,
ClaimTimeLock: claimTimeLock,
CollateralType: collateralType,
Start: start,
End: end,
Reward: reward,
ClaimEnd: claimEnd,
ClaimMultipliers: claimMultipliers,
}
}
@ -60,8 +60,8 @@ func (rp RewardPeriod) Validate() error {
if rp.ClaimEnd.IsZero() {
return errors.New("reward period claim end time cannot be 0")
}
if rp.ClaimTimeLock == 0 {
return errors.New("reward claim time lock cannot be 0")
if err := rp.ClaimMultipliers.Validate(); err != nil {
return err
}
if strings.TrimSpace(rp.CollateralType) == "" {
return fmt.Errorf("reward period collateral type cannot be blank: %s", rp)
@ -92,19 +92,19 @@ func (rps RewardPeriods) Validate() error {
// ClaimPeriod stores the state of an ongoing claim period
type ClaimPeriod struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
ID uint64 `json:"id" yaml:"id"`
End time.Time `json:"end" yaml:"end"`
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
ID uint64 `json:"id" yaml:"id"`
End time.Time `json:"end" yaml:"end"`
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
}
// 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{
CollateralType: collateralType,
ID: id,
End: end,
TimeLock: timeLock,
CollateralType: collateralType,
ID: id,
End: end,
ClaimMultipliers: multipliers,
}
}
@ -116,8 +116,8 @@ func (cp ClaimPeriod) Validate() error {
if cp.End.IsZero() {
return errors.New("claim period end time cannot be 0")
}
if cp.TimeLock == 0 {
return errors.New("claim period time lock cannot be 0")
if err := cp.ClaimMultipliers.Validate(); err != nil {
return err
}
if strings.TrimSpace(cp.CollateralType) == "" {
return fmt.Errorf("claim period collateral type cannot be blank: %s", cp)
@ -131,8 +131,18 @@ func (cp ClaimPeriod) String() string {
Collateral Type: %s,
ID: %d,
End: %s,
Claim Time Lock: %s
`, cp.CollateralType, cp.ID, cp.End, cp.TimeLock)
%s
`, 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
@ -261,11 +271,11 @@ func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
return RewardPeriod{
CollateralType: reward.CollateralType,
Start: blockTime,
End: blockTime.Add(reward.Duration),
Reward: rewardCoinPerSecond,
ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration),
ClaimTimeLock: reward.TimeLock,
CollateralType: reward.CollateralType,
Start: blockTime,
End: blockTime.Add(reward.Duration),
Reward: rewardCoinPerSecond,
ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration),
ClaimMultipliers: reward.ClaimMultipliers,
}
}

View File

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