mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
USDX incentives implementation (#399)
* USDX incentives implementation (#399) * feat: upgrade to cosmos-sdk v0.38 Co-authored-by: Denali Marsh <denali@kava.io> Co-authored-by: John Maheswaran <jmaheswaran@users.noreply.github.com> Co-authored-by: John Maheswaran <john@kava.io>
This commit is contained in:
parent
5737f4fa19
commit
1ef9bd331b
20
app/app.go
20
app/app.go
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/auction"
|
"github.com/kava-labs/kava/x/auction"
|
||||||
"github.com/kava-labs/kava/x/bep3"
|
"github.com/kava-labs/kava/x/bep3"
|
||||||
"github.com/kava-labs/kava/x/cdp"
|
"github.com/kava-labs/kava/x/cdp"
|
||||||
|
"github.com/kava-labs/kava/x/incentive"
|
||||||
"github.com/kava-labs/kava/x/kavadist"
|
"github.com/kava-labs/kava/x/kavadist"
|
||||||
"github.com/kava-labs/kava/x/pricefeed"
|
"github.com/kava-labs/kava/x/pricefeed"
|
||||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
@ -69,6 +70,7 @@ var (
|
|||||||
pricefeed.AppModuleBasic{},
|
pricefeed.AppModuleBasic{},
|
||||||
bep3.AppModuleBasic{},
|
bep3.AppModuleBasic{},
|
||||||
kavadist.AppModuleBasic{},
|
kavadist.AppModuleBasic{},
|
||||||
|
incentive.AppModuleBasic{},
|
||||||
)
|
)
|
||||||
|
|
||||||
// module account permissions
|
// module account permissions
|
||||||
@ -121,6 +123,7 @@ type App struct {
|
|||||||
pricefeedKeeper pricefeed.Keeper
|
pricefeedKeeper pricefeed.Keeper
|
||||||
bep3Keeper bep3.Keeper
|
bep3Keeper bep3.Keeper
|
||||||
kavadistKeeper kavadist.Keeper
|
kavadistKeeper kavadist.Keeper
|
||||||
|
incentiveKeeper incentive.Keeper
|
||||||
|
|
||||||
// the module manager
|
// the module manager
|
||||||
mm *module.Manager
|
mm *module.Manager
|
||||||
@ -145,7 +148,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
|
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
|
||||||
gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey,
|
gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey,
|
||||||
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey,
|
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey,
|
||||||
kavadist.StoreKey,
|
kavadist.StoreKey, incentive.StoreKey,
|
||||||
)
|
)
|
||||||
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
||||||
|
|
||||||
@ -173,6 +176,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
|
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
|
||||||
bep3Subspace := app.paramsKeeper.Subspace(bep3.DefaultParamspace)
|
bep3Subspace := app.paramsKeeper.Subspace(bep3.DefaultParamspace)
|
||||||
kavadistSubspace := app.paramsKeeper.Subspace(kavadist.DefaultParamspace)
|
kavadistSubspace := app.paramsKeeper.Subspace(kavadist.DefaultParamspace)
|
||||||
|
incentiveSubspace := app.paramsKeeper.Subspace(incentive.DefaultParamspace)
|
||||||
|
|
||||||
// add keepers
|
// add keepers
|
||||||
app.accountKeeper = auth.NewAccountKeeper(
|
app.accountKeeper = auth.NewAccountKeeper(
|
||||||
@ -295,6 +299,14 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
kavadistSubspace,
|
kavadistSubspace,
|
||||||
app.supplyKeeper,
|
app.supplyKeeper,
|
||||||
)
|
)
|
||||||
|
app.incentiveKeeper = incentive.NewKeeper(
|
||||||
|
app.cdc,
|
||||||
|
keys[incentive.StoreKey],
|
||||||
|
incentiveSubspace,
|
||||||
|
app.supplyKeeper,
|
||||||
|
app.cdpKeeper,
|
||||||
|
app.accountKeeper,
|
||||||
|
)
|
||||||
|
|
||||||
// register the staking hooks
|
// register the staking hooks
|
||||||
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
||||||
@ -321,12 +333,13 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
|
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
|
||||||
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
||||||
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
||||||
|
incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper),
|
||||||
)
|
)
|
||||||
|
|
||||||
// During begin block slashing happens after distr.BeginBlocker so that
|
// During begin block slashing happens after distr.BeginBlocker so that
|
||||||
// there is nothing left over in the validator fee pool, so as to keep the
|
// there is nothing left over in the validator fee pool, so as to keep the
|
||||||
// CanWithdrawInvariant invariant.
|
// CanWithdrawInvariant invariant.
|
||||||
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName)
|
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, incentive.ModuleName)
|
||||||
|
|
||||||
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
|
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
|
||||||
|
|
||||||
@ -335,7 +348,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
validatorvesting.ModuleName, distr.ModuleName,
|
validatorvesting.ModuleName, distr.ModuleName,
|
||||||
staking.ModuleName, bank.ModuleName, slashing.ModuleName,
|
staking.ModuleName, bank.ModuleName, slashing.ModuleName,
|
||||||
gov.ModuleName, mint.ModuleName, evidence.ModuleName,
|
gov.ModuleName, mint.ModuleName, evidence.ModuleName,
|
||||||
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok?
|
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName,
|
||||||
supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis
|
supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis
|
||||||
crisis.ModuleName, // runs the invariants at genesis - should run after other modules
|
crisis.ModuleName, // runs the invariants at genesis - should run after other modules
|
||||||
genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts.
|
genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts.
|
||||||
@ -363,6 +376,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
|
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
|
||||||
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
||||||
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
||||||
|
incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper),
|
||||||
)
|
)
|
||||||
|
|
||||||
app.sm.RegisterStoreDecoders()
|
app.sm.RegisterStoreDecoders()
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/auction"
|
"github.com/kava-labs/kava/x/auction"
|
||||||
"github.com/kava-labs/kava/x/bep3"
|
"github.com/kava-labs/kava/x/bep3"
|
||||||
"github.com/kava-labs/kava/x/cdp"
|
"github.com/kava-labs/kava/x/cdp"
|
||||||
|
"github.com/kava-labs/kava/x/incentive"
|
||||||
"github.com/kava-labs/kava/x/kavadist"
|
"github.com/kava-labs/kava/x/kavadist"
|
||||||
"github.com/kava-labs/kava/x/pricefeed"
|
"github.com/kava-labs/kava/x/pricefeed"
|
||||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
@ -75,6 +76,7 @@ func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeepe
|
|||||||
func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper }
|
func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper }
|
||||||
func (tApp TestApp) GetBep3Keeper() bep3.Keeper { return tApp.bep3Keeper }
|
func (tApp TestApp) GetBep3Keeper() bep3.Keeper { return tApp.bep3Keeper }
|
||||||
func (tApp TestApp) GetKavadistKeeper() kavadist.Keeper { return tApp.kavadistKeeper }
|
func (tApp TestApp) GetKavadistKeeper() kavadist.Keeper { return tApp.kavadistKeeper }
|
||||||
|
func (tApp TestApp) GetIncentiveKeeper() incentive.Keeper { return tApp.incentiveKeeper }
|
||||||
|
|
||||||
// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states
|
// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states
|
||||||
func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp {
|
func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp {
|
||||||
|
1
go.sum
1
go.sum
@ -416,6 +416,7 @@ github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1Tuol
|
|||||||
github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
|
github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
|
||||||
github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI=
|
github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI=
|
||||||
github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
|
github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
|
||||||
|
github.com/tendermint/tendermint v0.33.4 h1:NM3G9618yC5PaaxGrcAySc5ylc1PAANeIx42u2Re/jo=
|
||||||
github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY=
|
github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY=
|
||||||
github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ=
|
github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ=
|
||||||
github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U=
|
github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U=
|
||||||
|
@ -37,7 +37,6 @@ func queryAtomicSwapHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
swapID, err := types.HexToBytes(vars[restSwapID])
|
swapID, err := types.HexToBytes(vars[restSwapID])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
13
x/incentive/abci.go
Normal file
13
x/incentive/abci.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package incentive
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeginBlocker runs at the start of every block
|
||||||
|
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
|
k.DeleteExpiredClaimsAndClaimPeriods(ctx)
|
||||||
|
k.ApplyRewardsToCdps(ctx)
|
||||||
|
k.CreateAndDeleteRewardPeriods(ctx)
|
||||||
|
}
|
92
x/incentive/alias.go
Normal file
92
x/incentive/alias.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// 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
|
||||||
|
package incentive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventTypeClaim = types.EventTypeClaim
|
||||||
|
AttributeValueCategory = types.AttributeValueCategory
|
||||||
|
AttributeKeySender = types.AttributeKeySender
|
||||||
|
ModuleName = types.ModuleName
|
||||||
|
StoreKey = types.StoreKey
|
||||||
|
RouterKey = types.RouterKey
|
||||||
|
DefaultParamspace = types.DefaultParamspace
|
||||||
|
QuerierRoute = types.QuerierRoute
|
||||||
|
QueryGetClaims = types.QueryGetClaims
|
||||||
|
RestClaimOwner = types.RestClaimOwner
|
||||||
|
RestClaimDenom = types.RestClaimDenom
|
||||||
|
QueryGetParams = types.QueryGetParams
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// functions aliases
|
||||||
|
NewKeeper = keeper.NewKeeper
|
||||||
|
NewQuerier = keeper.NewQuerier
|
||||||
|
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
|
||||||
|
RegisterCodec = types.RegisterCodec
|
||||||
|
NewGenesisState = types.NewGenesisState
|
||||||
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
|
BytesToUint64 = types.BytesToUint64
|
||||||
|
GetClaimPeriodPrefix = types.GetClaimPeriodPrefix
|
||||||
|
GetClaimPrefix = types.GetClaimPrefix
|
||||||
|
NewMsgClaimReward = types.NewMsgClaimReward
|
||||||
|
NewParams = types.NewParams
|
||||||
|
DefaultParams = types.DefaultParams
|
||||||
|
ParamKeyTable = types.ParamKeyTable
|
||||||
|
NewReward = types.NewReward
|
||||||
|
NewPeriod = types.NewPeriod
|
||||||
|
NewQueryClaimsParams = types.NewQueryClaimsParams
|
||||||
|
NewRewardPeriod = types.NewRewardPeriod
|
||||||
|
NewClaimPeriod = types.NewClaimPeriod
|
||||||
|
NewClaim = types.NewClaim
|
||||||
|
|
||||||
|
// variable aliases
|
||||||
|
ModuleCdc = types.ModuleCdc
|
||||||
|
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
|
||||||
|
KeyActive = types.KeyActive
|
||||||
|
KeyRewards = types.KeyRewards
|
||||||
|
DefaultActive = types.DefaultActive
|
||||||
|
DefaultRewards = types.DefaultRewards
|
||||||
|
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
|
||||||
|
GovDenom = types.GovDenom
|
||||||
|
PrincipalDenom = types.PrincipalDenom
|
||||||
|
IncentiveMacc = types.IncentiveMacc
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Keeper = keeper.Keeper
|
||||||
|
SupplyKeeper = types.SupplyKeeper
|
||||||
|
CdpKeeper = types.CdpKeeper
|
||||||
|
AccountKeeper = types.AccountKeeper
|
||||||
|
GenesisClaimPeriodID = types.GenesisClaimPeriodID
|
||||||
|
GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs
|
||||||
|
GenesisState = types.GenesisState
|
||||||
|
MsgClaimReward = types.MsgClaimReward
|
||||||
|
Params = types.Params
|
||||||
|
Reward = types.Reward
|
||||||
|
Rewards = types.Rewards
|
||||||
|
QueryClaimsParams = types.QueryClaimsParams
|
||||||
|
PostClaimReq = types.PostClaimReq
|
||||||
|
RewardPeriod = types.RewardPeriod
|
||||||
|
RewardPeriods = types.RewardPeriods
|
||||||
|
ClaimPeriod = types.ClaimPeriod
|
||||||
|
ClaimPeriods = types.ClaimPeriods
|
||||||
|
Claim = types.Claim
|
||||||
|
Claims = types.Claims
|
||||||
|
)
|
98
x/incentive/client/cli/query.go
Normal file
98
x/incentive/client/cli/query.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetQueryCmd returns the cli query commands for the incentive module
|
||||||
|
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||||
|
incentiveQueryCmd := &cobra.Command{
|
||||||
|
Use: types.ModuleName,
|
||||||
|
Short: "Querying commands for the incentive module",
|
||||||
|
}
|
||||||
|
|
||||||
|
incentiveQueryCmd.AddCommand(flags.GetCommands(
|
||||||
|
queryParamsCmd(queryRoute, cdc),
|
||||||
|
queryClaimsCmd(queryRoute, cdc),
|
||||||
|
)...)
|
||||||
|
|
||||||
|
return incentiveQueryCmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "claims [owner-addr] [denom]",
|
||||||
|
Short: "get claims by onwer and denom",
|
||||||
|
Long: strings.TrimSpace(
|
||||||
|
fmt.Sprintf(`Get all claims owned by the owner address for the particular collateral type.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
$ %s query %s claims kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb`, version.ClientName, types.ModuleName)),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
// Prepare params for querier
|
||||||
|
ownerAddress, err := sdk.AccAddressFromBech32(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bz, err := cdc.MarshalJSON(types.QueryClaimsParams{
|
||||||
|
Owner: ownerAddress,
|
||||||
|
Denom: args[1],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query
|
||||||
|
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaims)
|
||||||
|
res, _, err := cliCtx.QueryWithData(route, bz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims types.Claims
|
||||||
|
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||||
|
}
|
||||||
|
return cliCtx.PrintOutput(claims)
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "params",
|
||||||
|
Short: "get the incentive module parameters",
|
||||||
|
Long: "Get the current global incentive module parameters.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
|
// Query
|
||||||
|
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetParams)
|
||||||
|
res, _, err := cliCtx.QueryWithData(route, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode and print results
|
||||||
|
var params types.Params
|
||||||
|
if err := cdc.UnmarshalJSON(res, ¶ms); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal params: %w", err)
|
||||||
|
}
|
||||||
|
return cliCtx.PrintOutput(params)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
63
x/incentive/client/cli/tx.go
Normal file
63
x/incentive/client/cli/tx.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTxCmd returns the transaction cli commands for the incentive module
|
||||||
|
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
|
incentiveTxCmd := &cobra.Command{
|
||||||
|
Use: types.ModuleName,
|
||||||
|
Short: "transaction commands for the incentive module",
|
||||||
|
}
|
||||||
|
|
||||||
|
incentiveTxCmd.AddCommand(flags.PostCommands(
|
||||||
|
getCmdClaim(cdc),
|
||||||
|
)...)
|
||||||
|
|
||||||
|
return incentiveTxCmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCmdClaim(cdc *codec.Codec) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "claim [owner] [denom]",
|
||||||
|
Short: "claim rewards for owner and denom",
|
||||||
|
Long: strings.TrimSpace(
|
||||||
|
fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input denom,
|
||||||
|
|
||||||
|
Example:
|
||||||
|
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb
|
||||||
|
`, version.ClientName, types.ModuleName),
|
||||||
|
),
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||||
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||||
|
owner, err := sdk.AccAddressFromBech32(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgClaimReward(owner, args[1])
|
||||||
|
err = msg.ValidateBasic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
70
x/incentive/client/rest/query.go
Normal file
70
x/incentive/client/rest/query.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||||
|
r.HandleFunc(fmt.Sprintf("/%s/claims", types.ModuleName), queryClaimsHandlerFn(cliCtx)).Methods("GET")
|
||||||
|
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
ownerBech32 := vars[types.RestClaimOwner]
|
||||||
|
denom := vars[types.RestClaimDenom]
|
||||||
|
|
||||||
|
owner, err := sdk.AccAddressFromBech32(ownerBech32)
|
||||||
|
if err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := types.NewQueryClaimsParams(owner, denom)
|
||||||
|
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
|
||||||
|
if err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetClaims), bz)
|
||||||
|
if err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cliCtx = cliCtx.WithHeight(height)
|
||||||
|
rest.PostProcessResponse(w, cliCtx, res)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
route := fmt.Sprintf("custom/%s/parameters", types.QuerierRoute)
|
||||||
|
|
||||||
|
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||||
|
if err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCtx = cliCtx.WithHeight(height)
|
||||||
|
rest.PostProcessResponse(w, cliCtx, res)
|
||||||
|
}
|
||||||
|
}
|
13
x/incentive/client/rest/rest.go
Normal file
13
x/incentive/client/rest/rest.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterRoutes registers incentive-related REST handlers to a router
|
||||||
|
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||||
|
registerQueryRoutes(cliCtx, r)
|
||||||
|
registerTxRoutes(cliCtx, r)
|
||||||
|
}
|
36
x/incentive/client/rest/tx.go
Normal file
36
x/incentive/client/rest/tx.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||||
|
r.HandleFunc("/incentive/claim", postClaimHandlerFn(cliCtx)).Methods("POST")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var requestBody types.PostClaimReq
|
||||||
|
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
|
||||||
|
if !requestBody.BaseReq.ValidateBasic(w) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.Denom)
|
||||||
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
|
||||||
|
}
|
||||||
|
}
|
40
x/incentive/doc.go
Normal file
40
x/incentive/doc.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Package incentive implements a Cosmos SDK module, per ADR 009, that provides governance-controlled,
|
||||||
|
on-chain incentives for users who open cdps and mint stablecoins (USDX).
|
||||||
|
|
||||||
|
For the background and motivation of this module, see the governance proposal that was voted on by
|
||||||
|
KAVA token holders: https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf
|
||||||
|
|
||||||
|
The 'Reward' parameter is used to control how much incentives are given.
|
||||||
|
For example, the following reward:
|
||||||
|
|
||||||
|
Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", 1000000000),
|
||||||
|
Duration: time.Hour*7*24,
|
||||||
|
TimeLock: time.Hour*24*365,
|
||||||
|
ClaimDuration: time.Hour*7*24,
|
||||||
|
}
|
||||||
|
|
||||||
|
will distribute 1000 KAVA each week (Duration) to users who mint USDX using collateral bnb.
|
||||||
|
That KAVA can be claimed by the user for one week (ClaimDuration) after the reward period expires,
|
||||||
|
and all KAVA rewards will be timelocked for 1 year (TimeLock). If a user does not claim them during
|
||||||
|
the claim duration period, they are forgone.
|
||||||
|
|
||||||
|
Rewards are accumulated by users continuously and proportionally - ie. if a user holds a CDP that has
|
||||||
|
minted 10% of all USDX backed by bnb for the entire reward period, they will be eligible to claim 10%
|
||||||
|
of rewards for that period.
|
||||||
|
|
||||||
|
Once a reward period ends, but not before, users can claim the rewards they have accumulated. Users claim rewards
|
||||||
|
using a MsgClaimReward transaction. The following msg:
|
||||||
|
|
||||||
|
MsgClaimReward {
|
||||||
|
"kava1..."
|
||||||
|
"bnb"
|
||||||
|
}
|
||||||
|
|
||||||
|
will claim all outstanding rewards for minting USDX backed by bnb for the input user.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package incentive
|
73
x/incentive/genesis.go
Normal file
73
x/incentive/genesis.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package incentive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
|
func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, gs types.GenesisState) {
|
||||||
|
|
||||||
|
// check if the module account exists
|
||||||
|
moduleAcc := supplyKeeper.GetModuleAccount(ctx, types.IncentiveMacc)
|
||||||
|
if moduleAcc == nil {
|
||||||
|
panic(fmt.Sprintf("%s module account has not been set", types.IncentiveMacc))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gs.Validate(); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
k.SetParams(ctx, gs.Params)
|
||||||
|
|
||||||
|
for _, r := range gs.Params.Rewards {
|
||||||
|
k.SetNextClaimPeriodID(ctx, r.Denom, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only set the previous block time if it's different than default
|
||||||
|
if !gs.PreviousBlockTime.Equal(types.DefaultPreviousBlockTime) {
|
||||||
|
k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set store objects
|
||||||
|
for _, rp := range gs.RewardPeriods {
|
||||||
|
k.SetRewardPeriod(ctx, rp)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cp := range gs.ClaimPeriods {
|
||||||
|
k.SetClaimPeriod(ctx, cp)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range gs.Claims {
|
||||||
|
k.SetClaim(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range gs.NextClaimPeriodIDs {
|
||||||
|
k.SetNextClaimPeriodID(ctx, id.Denom, id.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis export genesis state for incentive module
|
||||||
|
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
||||||
|
// get all objects out of the store
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||||
|
|
||||||
|
// since it is not set in genesis, if somehow the chain got started and was exported
|
||||||
|
// immediately after InitGenesis, there would be no previousBlockTime value.
|
||||||
|
if !found {
|
||||||
|
previousBlockTime = types.DefaultPreviousBlockTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all objects from the store
|
||||||
|
rewardPeriods := k.GetAllRewardPeriods(ctx)
|
||||||
|
claimPeriods := k.GetAllClaimPeriods(ctx)
|
||||||
|
claims := k.GetAllClaims(ctx)
|
||||||
|
claimPeriodIDs := k.GetAllClaimPeriodIDPairs(ctx)
|
||||||
|
|
||||||
|
return types.NewGenesisState(params, previousBlockTime, rewardPeriods, claimPeriods, claims, claimPeriodIDs)
|
||||||
|
}
|
48
x/incentive/handler.go
Normal file
48
x/incentive/handler.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package incentive
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHandler creates an sdk.Handler for incentive module messages
|
||||||
|
func NewHandler(k keeper.Keeper) sdk.Handler {
|
||||||
|
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||||
|
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case types.MsgClaimReward:
|
||||||
|
return handleMsgClaimReward(ctx, k, msg)
|
||||||
|
default:
|
||||||
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimReward) (*sdk.Result, error) {
|
||||||
|
|
||||||
|
claims, found := k.GetClaimsByAddressAndDenom(ctx, msg.Sender, msg.Denom)
|
||||||
|
if !found {
|
||||||
|
return nil, sdkerrors.Wrapf(types.ErrNoClaimsFound, "address: %s, denom: %s", msg.Sender, msg.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, claim := range claims {
|
||||||
|
err := k.PayoutClaim(ctx, claim.Owner, claim.Denom, claim.ClaimPeriodID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
sdk.EventTypeMessage,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &sdk.Result{
|
||||||
|
Events: ctx.EventManager().Events(),
|
||||||
|
}, nil
|
||||||
|
}
|
243
x/incentive/keeper/keeper.go
Normal file
243
x/incentive/keeper/keeper.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keeper keeper for the incentive module
|
||||||
|
type Keeper struct {
|
||||||
|
accountKeeper types.AccountKeeper
|
||||||
|
cdc *codec.Codec
|
||||||
|
cdpKeeper types.CdpKeeper
|
||||||
|
key sdk.StoreKey
|
||||||
|
paramSubspace subspace.Subspace
|
||||||
|
supplyKeeper types.SupplyKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeeper creates a new keeper
|
||||||
|
func NewKeeper(
|
||||||
|
cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper,
|
||||||
|
cdpk types.CdpKeeper, ak types.AccountKeeper,
|
||||||
|
) Keeper {
|
||||||
|
|
||||||
|
return Keeper{
|
||||||
|
accountKeeper: ak,
|
||||||
|
cdc: cdc,
|
||||||
|
cdpKeeper: cdpk,
|
||||||
|
key: key,
|
||||||
|
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
|
||||||
|
supplyKeeper: sk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRewardPeriod returns the reward period from the store for the input denom and a boolean for if it was found
|
||||||
|
func (k Keeper) GetRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||||
|
bz := store.Get([]byte(denom))
|
||||||
|
if bz == nil {
|
||||||
|
return types.RewardPeriod{}, false
|
||||||
|
}
|
||||||
|
var rp types.RewardPeriod
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &rp)
|
||||||
|
return rp, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRewardPeriod sets the reward period in the store for the input deno,
|
||||||
|
func (k Keeper) SetRewardPeriod(ctx sdk.Context, rp types.RewardPeriod) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshalBinaryBare(rp)
|
||||||
|
store.Set([]byte(rp.Denom), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRewardPeriod deletes the reward period in the store for the input denom,
|
||||||
|
func (k Keeper) DeleteRewardPeriod(ctx sdk.Context, denom string) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||||
|
store.Delete([]byte(denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateRewardPeriods iterates over all reward period objects in the store and preforms a callback function
|
||||||
|
func (k Keeper) IterateRewardPeriods(ctx sdk.Context, cb func(rp types.RewardPeriod) (stop bool)) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var rp types.RewardPeriod
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &rp)
|
||||||
|
if cb(rp) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRewardPeriods returns all reward periods in the store
|
||||||
|
func (k Keeper) GetAllRewardPeriods(ctx sdk.Context) types.RewardPeriods {
|
||||||
|
rps := types.RewardPeriods{}
|
||||||
|
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) (stop bool) {
|
||||||
|
rps = append(rps, rp)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return rps
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextClaimPeriodID returns the highest claim period id in the store for the input denom
|
||||||
|
func (k Keeper) GetNextClaimPeriodID(ctx sdk.Context, denom string) uint64 {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||||
|
bz := store.Get([]byte(denom))
|
||||||
|
return types.BytesToUint64(bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNextClaimPeriodID sets the highest claim period id in the store for the input denom
|
||||||
|
func (k Keeper) SetNextClaimPeriodID(ctx sdk.Context, denom string, id uint64) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||||
|
store.Set([]byte(denom), sdk.Uint64ToBigEndian(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateClaimPeriodIDKeysAndValues iterates over the claim period id (value) and denom (key) of each claim period id in the store and performs a callback function
|
||||||
|
func (k Keeper) IterateClaimPeriodIDKeysAndValues(ctx sdk.Context, cb func(denom string, id uint64) (stop bool)) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
id := types.BytesToUint64(iterator.Value())
|
||||||
|
denom := string(iterator.Key())
|
||||||
|
if cb(denom, id) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllClaimPeriodIDPairs returns all denom:nextClaimPeriodID pairs in the store
|
||||||
|
func (k Keeper) GetAllClaimPeriodIDPairs(ctx sdk.Context) types.GenesisClaimPeriodIDs {
|
||||||
|
ids := types.GenesisClaimPeriodIDs{}
|
||||||
|
k.IterateClaimPeriodIDKeysAndValues(ctx, func(denom string, id uint64) (stop bool) {
|
||||||
|
genID := types.GenesisClaimPeriodID{
|
||||||
|
Denom: denom,
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
ids = append(ids, genID)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaimPeriod returns claim period in the store for the input ID and denom and a boolean for if it was found
|
||||||
|
func (k Keeper) GetClaimPeriod(ctx sdk.Context, id uint64, denom string) (types.ClaimPeriod, bool) {
|
||||||
|
var cp types.ClaimPeriod
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||||
|
bz := store.Get(types.GetClaimPeriodPrefix(denom, id))
|
||||||
|
if bz == nil {
|
||||||
|
return types.ClaimPeriod{}, false
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &cp)
|
||||||
|
return cp, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClaimPeriod sets the claim period in the store for the input ID and denom
|
||||||
|
func (k Keeper) SetClaimPeriod(ctx sdk.Context, cp types.ClaimPeriod) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshalBinaryBare(cp)
|
||||||
|
store.Set(types.GetClaimPeriodPrefix(cp.Denom, cp.ID), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClaimPeriod deletes the claim period in the store for the input ID and denom
|
||||||
|
func (k Keeper) DeleteClaimPeriod(ctx sdk.Context, id uint64, denom string) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||||
|
store.Delete(types.GetClaimPeriodPrefix(denom, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateClaimPeriods iterates over all claim period objects in the store and preforms a callback function
|
||||||
|
func (k Keeper) IterateClaimPeriods(ctx sdk.Context, cb func(cp types.ClaimPeriod) (stop bool)) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var cp types.ClaimPeriod
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &cp)
|
||||||
|
if cb(cp) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllClaimPeriods returns all ClaimPeriod objects in the store
|
||||||
|
func (k Keeper) GetAllClaimPeriods(ctx sdk.Context) types.ClaimPeriods {
|
||||||
|
cps := types.ClaimPeriods{}
|
||||||
|
k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) {
|
||||||
|
cps = append(cps, cp)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return cps
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaim returns the claim in the store corresponding the the input address denom and id and a boolean for if the claim was found
|
||||||
|
func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64) (types.Claim, bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
|
bz := store.Get(types.GetClaimPrefix(addr, denom, id))
|
||||||
|
if bz == nil {
|
||||||
|
return types.Claim{}, false
|
||||||
|
}
|
||||||
|
var c types.Claim
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &c)
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClaim sets the claim in the store corresponding to the input address, denom, and id
|
||||||
|
func (k Keeper) SetClaim(ctx sdk.Context, c types.Claim) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
|
bz := k.cdc.MustMarshalBinaryBare(c)
|
||||||
|
store.Set(types.GetClaimPrefix(c.Owner, c.Denom, c.ClaimPeriodID), bz)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClaim deletes the claim in the store corresponding to the input address, denom, and id
|
||||||
|
func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress, denom string, id uint64) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
|
store.Delete(types.GetClaimPrefix(owner, denom, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateClaims iterates over all claim objects in the store and preforms a callback function
|
||||||
|
func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.Claim) (stop bool)) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var c types.Claim
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c)
|
||||||
|
if cb(c) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllClaims returns all Claim objects in the store
|
||||||
|
func (k Keeper) GetAllClaims(ctx sdk.Context) types.Claims {
|
||||||
|
cs := types.Claims{}
|
||||||
|
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
|
||||||
|
cs = append(cs, c)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreviousBlockTime get the blocktime for the previous block
|
||||||
|
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||||
|
b := store.Get([]byte{})
|
||||||
|
if b == nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(b, &blockTime)
|
||||||
|
return blockTime, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreviousBlockTime set the time of the previous block
|
||||||
|
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||||
|
store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime))
|
||||||
|
}
|
18
x/incentive/keeper/params.go
Normal file
18
x/incentive/keeper/params.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetParams returns the params from the store
|
||||||
|
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
||||||
|
var p types.Params
|
||||||
|
k.paramSubspace.GetParamSet(ctx, &p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetParams sets params on the store
|
||||||
|
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||||
|
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||||
|
}
|
204
x/incentive/keeper/payout.go
Normal file
204
x/incentive/keeper/payout.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
|
supplyExported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PayoutClaim sends the timelocked claim coins to the input address
|
||||||
|
func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64) error {
|
||||||
|
claim, found := k.GetClaim(ctx, addr, denom, id)
|
||||||
|
if !found {
|
||||||
|
return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, denom %s, address: %s", id, denom, addr)
|
||||||
|
}
|
||||||
|
claimPeriod, found := k.GetClaimPeriod(ctx, id, denom)
|
||||||
|
if !found {
|
||||||
|
return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, denom: %s", id, denom)
|
||||||
|
}
|
||||||
|
err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(claim.Reward), int64(claimPeriod.TimeLock.Seconds()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvent(
|
||||||
|
sdk.NewEvent(
|
||||||
|
types.EventTypeClaim,
|
||||||
|
sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", addr)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTimeLockedCoinsToAccount sends time-locked coins from the input module account to the recipient. If the recipients account is not a vesting account, it is converted to a periodic vesting account and the coins are added to the vesting balance as a vesting period with the input length.
|
||||||
|
func (k Keeper) SendTimeLockedCoinsToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
|
||||||
|
macc := k.supplyKeeper.GetModuleAccount(ctx, senderModule)
|
||||||
|
if !macc.GetCoins().IsAllGTE(amt) {
|
||||||
|
return sdkerrors.Wrapf(types.ErrInsufficientModAccountBalance, "%s", senderModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0. Get the account from the account keeper and do a type switch, error if it's a validator vesting account or module account (can make this work for validator vesting later if necessary)
|
||||||
|
acc := k.accountKeeper.GetAccount(ctx, recipientAddr)
|
||||||
|
|
||||||
|
switch acc.(type) {
|
||||||
|
case *validatorvesting.ValidatorVestingAccount, supplyExported.ModuleAccountI:
|
||||||
|
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", acc)
|
||||||
|
case *vesting.PeriodicVestingAccount:
|
||||||
|
return k.SendTimeLockedCoinsToPeriodicVestingAccount(ctx, senderModule, recipientAddr, amt, length)
|
||||||
|
case *auth.BaseAccount:
|
||||||
|
return k.SendTimeLockedCoinsToBaseAccount(ctx, senderModule, recipientAddr, amt, length)
|
||||||
|
default:
|
||||||
|
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTimeLockedCoinsToPeriodicVestingAccount sends time-locked coins from the input module account to the recipient
|
||||||
|
func (k Keeper) SendTimeLockedCoinsToPeriodicVestingAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
|
||||||
|
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.addCoinsToVestingSchedule(ctx, recipientAddr, amt, length)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTimeLockedCoinsToBaseAccount sends time-locked coins from the input module account to the recipient, converting the recipient account to a vesting account
|
||||||
|
func (k Keeper) SendTimeLockedCoinsToBaseAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
|
||||||
|
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acc := k.accountKeeper.GetAccount(ctx, recipientAddr)
|
||||||
|
// transition the account to a periodic vesting account:
|
||||||
|
bacc := authtypes.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||||
|
newPeriods := vesting.Periods{types.NewPeriod(amt, length)}
|
||||||
|
bva, err := vesting.NewBaseVestingAccount(bacc, amt, ctx.BlockTime().Unix()+length)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), newPeriods)
|
||||||
|
k.accountKeeper.SetAccount(ctx, pva)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteExpiredClaimsAndClaimPeriods deletes expired claim periods and their associated claims
|
||||||
|
func (k Keeper) DeleteExpiredClaimsAndClaimPeriods(ctx sdk.Context) {
|
||||||
|
k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) {
|
||||||
|
if !cp.End.Before(ctx.BlockTime()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
|
||||||
|
if !(c.Denom == cp.Denom && c.ClaimPeriodID == cp.ID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.DeleteClaim(ctx, c.Owner, c.Denom, c.ClaimPeriodID)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
k.DeleteClaimPeriod(ctx, cp.ID, cp.Denom)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaimsByAddressAndDenom returns all claims for a specific user and address and a bool for if any were found
|
||||||
|
func (k Keeper) GetClaimsByAddressAndDenom(ctx sdk.Context, addr sdk.AccAddress, denom string) (claims types.Claims, found bool) {
|
||||||
|
found = false
|
||||||
|
k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) {
|
||||||
|
if cp.Denom != denom {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c, hasClaim := k.GetClaim(ctx, addr, cp.Denom, cp.ID)
|
||||||
|
if !hasClaim {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
claims = append(claims, c)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return claims, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCoinsToVestingSchedule adds coins to the input account's vesting schedule where length is the amount of time (from the current block time), in seconds, that the coins will be vesting for
|
||||||
|
// the input address must be a periodic vesting account
|
||||||
|
func (k Keeper) addCoinsToVestingSchedule(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, length int64) {
|
||||||
|
acc := k.accountKeeper.GetAccount(ctx, addr)
|
||||||
|
vacc := acc.(*vesting.PeriodicVestingAccount)
|
||||||
|
// Add the new vesting coins to OriginalVesting
|
||||||
|
vacc.OriginalVesting = vacc.OriginalVesting.Add(amt...)
|
||||||
|
// update vesting periods
|
||||||
|
if vacc.EndTime < ctx.BlockTime().Unix() {
|
||||||
|
// edge case one - the vesting account's end time is in the past (ie, all previous vesting periods have completed)
|
||||||
|
// append a new period to the vesting account, update the end time, update the account in the store and return
|
||||||
|
newPeriodLength := (ctx.BlockTime().Unix() - vacc.EndTime) + length
|
||||||
|
newPeriod := types.NewPeriod(amt, newPeriodLength)
|
||||||
|
vacc.VestingPeriods = append(vacc.VestingPeriods, newPeriod)
|
||||||
|
vacc.EndTime = ctx.BlockTime().Unix() + length
|
||||||
|
k.accountKeeper.SetAccount(ctx, vacc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if vacc.StartTime > ctx.BlockTime().Unix() {
|
||||||
|
// edge case two - the vesting account's start time is in the future (all periods have not started)
|
||||||
|
// update the start time to now and adjust the period lengths in place - a new period will be inserted in the next code block
|
||||||
|
updatedPeriods := vesting.Periods{}
|
||||||
|
for i, period := range vacc.VestingPeriods {
|
||||||
|
updatedPeriod := period
|
||||||
|
if i == 0 {
|
||||||
|
updatedPeriod = types.NewPeriod(period.Amount, (vacc.StartTime-ctx.BlockTime().Unix())+period.Length)
|
||||||
|
}
|
||||||
|
updatedPeriods = append(updatedPeriods, updatedPeriod)
|
||||||
|
}
|
||||||
|
vacc.VestingPeriods = updatedPeriods
|
||||||
|
vacc.StartTime = ctx.BlockTime().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// logic for inserting a new vesting period into the existing vesting schedule
|
||||||
|
totalPeriodLength := types.GetTotalVestingPeriodLength(vacc.VestingPeriods)
|
||||||
|
proposedEndTime := ctx.BlockTime().Unix() + length
|
||||||
|
if totalPeriodLength < length {
|
||||||
|
// in the case that the proposed length is longer than the sum of all previous period lengths, create a new period with length equal to the difference between the proposed length and the previous total length
|
||||||
|
newPeriodLength := length - totalPeriodLength
|
||||||
|
newPeriod := types.NewPeriod(amt, newPeriodLength)
|
||||||
|
vacc.VestingPeriods = append(vacc.VestingPeriods, newPeriod)
|
||||||
|
// update the end time so that the sum of all period lengths equals endTime - startTime
|
||||||
|
vacc.EndTime = proposedEndTime
|
||||||
|
} else {
|
||||||
|
// In the case that the proposed length is less than or equal to the sum of all previous period lengths, insert the period and update other periods as necessary.
|
||||||
|
// EXAMPLE (l is length, a is amount)
|
||||||
|
// Original Periods: {[l: 1 a: 1], [l: 2, a: 1], [l:8, a:3], [l: 5, a: 3]}
|
||||||
|
// Period we want to insert [l: 5, a: x]
|
||||||
|
// Expected result:
|
||||||
|
// {[l: 1, a: 1], [l:2, a: 1], [l:2, a:x], [l:6, a:3], [l:5, a:3]}
|
||||||
|
|
||||||
|
newPeriods := vesting.Periods{}
|
||||||
|
lengthCounter := int64(0)
|
||||||
|
appendRemaining := false
|
||||||
|
for _, period := range vacc.VestingPeriods {
|
||||||
|
if appendRemaining {
|
||||||
|
newPeriods = append(newPeriods, period)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lengthCounter += period.Length
|
||||||
|
if lengthCounter < length {
|
||||||
|
newPeriods = append(newPeriods, period)
|
||||||
|
} else if lengthCounter == length {
|
||||||
|
newPeriod := types.NewPeriod(period.Amount.Add(amt...), period.Length)
|
||||||
|
newPeriods = append(newPeriods, newPeriod)
|
||||||
|
appendRemaining = true
|
||||||
|
} else {
|
||||||
|
newPeriod := types.NewPeriod(amt, length-types.GetTotalVestingPeriodLength(newPeriods))
|
||||||
|
previousPeriod := types.NewPeriod(period.Amount, period.Length-newPeriod.Length)
|
||||||
|
newPeriods = append(newPeriods, newPeriod, previousPeriod)
|
||||||
|
appendRemaining = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vacc.VestingPeriods = newPeriods
|
||||||
|
}
|
||||||
|
k.accountKeeper.SetAccount(ctx, vacc)
|
||||||
|
return
|
||||||
|
}
|
51
x/incentive/keeper/querier.go
Normal file
51
x/incentive/keeper/querier.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewQuerier is the module level router for state queries
|
||||||
|
func NewQuerier(k Keeper) sdk.Querier {
|
||||||
|
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
|
||||||
|
switch path[0] {
|
||||||
|
case types.QueryGetParams:
|
||||||
|
return queryGetParams(ctx, req, k)
|
||||||
|
case types.QueryGetClaims:
|
||||||
|
return queryGetClaims(ctx, req, k)
|
||||||
|
default:
|
||||||
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// query params in the store
|
||||||
|
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||||
|
// Get params
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
|
||||||
|
// Encode results
|
||||||
|
bz, err := codec.MarshalJSONIndent(k.cdc, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||||
|
}
|
||||||
|
return bz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||||
|
var requestParams types.QueryClaimsParams
|
||||||
|
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||||
|
}
|
||||||
|
claims, _ := k.GetClaimsByAddressAndDenom(ctx, requestParams.Owner, requestParams.Denom)
|
||||||
|
|
||||||
|
bz, err := codec.MarshalJSONIndent(k.cdc, claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||||
|
}
|
||||||
|
return bz, nil
|
||||||
|
}
|
110
x/incentive/keeper/rewards.go
Normal file
110
x/incentive/keeper/rewards.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.Denom, rp.ClaimEnd, rp.ClaimTimeLock)
|
||||||
|
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||||
|
store.Delete([]byte(rp.Denom))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewRewardPeriod creates a new reward period from the input reward
|
||||||
|
func (k Keeper) CreateNewRewardPeriod(ctx sdk.Context, reward types.Reward) {
|
||||||
|
// reward periods store the amount of rewards payed PER SECOND
|
||||||
|
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
|
||||||
|
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
|
||||||
|
rp := types.RewardPeriod{
|
||||||
|
Denom: reward.Denom,
|
||||||
|
Start: ctx.BlockTime(),
|
||||||
|
End: ctx.BlockTime().Add(reward.Duration),
|
||||||
|
Reward: rewardCoinPerSecond,
|
||||||
|
ClaimEnd: ctx.BlockTime().Add(reward.Duration).Add(reward.ClaimDuration),
|
||||||
|
ClaimTimeLock: reward.TimeLock,
|
||||||
|
}
|
||||||
|
k.SetRewardPeriod(ctx, rp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAndDeleteRewardPeriods creates reward periods for active rewards that don't already have a reward period and deletes reward periods for inactive rewards that currently have a reward period
|
||||||
|
func (k Keeper) CreateAndDeleteRewardPeriods(ctx sdk.Context) {
|
||||||
|
params := k.GetParams(ctx)
|
||||||
|
|
||||||
|
for _, r := range params.Rewards {
|
||||||
|
_, found := k.GetRewardPeriod(ctx, r.Denom)
|
||||||
|
// if governance has made a reward inactive, delete the current period
|
||||||
|
if found && !r.Active {
|
||||||
|
k.DeleteRewardPeriod(ctx, r.Denom)
|
||||||
|
}
|
||||||
|
// if a reward period for an active reward is not found, create one
|
||||||
|
if !found && r.Active {
|
||||||
|
k.CreateNewRewardPeriod(ctx, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyRewardsToCdps iterates over the reward periods and creates a claim for each cdp owner that created usdx with the collateral specified in the reward period
|
||||||
|
func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
|
||||||
|
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||||
|
if !found {
|
||||||
|
previousBlockTime = ctx.BlockTime()
|
||||||
|
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool {
|
||||||
|
expired := false
|
||||||
|
// the total amount of usdx created with the collateral type being incentivized
|
||||||
|
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rp.Denom, types.PrincipalDenom)
|
||||||
|
// the number of seconds since last payout
|
||||||
|
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
|
||||||
|
if rp.End.Before(ctx.BlockTime()) {
|
||||||
|
timeElapsed = sdk.NewInt(rp.End.Unix() - previousBlockTime.Unix())
|
||||||
|
expired = true
|
||||||
|
}
|
||||||
|
// the amount of rewards to pay (rewardAmount * timeElapsed)
|
||||||
|
rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed)
|
||||||
|
id := k.GetNextClaimPeriodID(ctx, rp.Denom)
|
||||||
|
k.cdpKeeper.IterateCdpsByDenom(ctx, rp.Denom, func(cdp cdptypes.CDP) bool {
|
||||||
|
rewardsShare := sdk.NewDecFromInt(cdp.Principal.AmountOf(types.PrincipalDenom).Add(cdp.AccumulatedFees.AmountOf(types.PrincipalDenom))).Quo(sdk.NewDecFromInt(totalPrincipal))
|
||||||
|
// sanity check - don't create zero claims
|
||||||
|
if rewardsShare.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsThisPeriod)).RoundInt()
|
||||||
|
k.AddToClaim(ctx, cdp.Owner, rp.Denom, id, sdk.NewCoin(types.GovDenom, rewardsEarned))
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if !expired {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.HandleRewardPeriodExpiry(ctx, rp)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUniqueClaimPeriod creates a new claim period in the store and updates the highest claim period id
|
||||||
|
func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, denom string, end time.Time, timeLock time.Duration) {
|
||||||
|
id := k.GetNextClaimPeriodID(ctx, denom)
|
||||||
|
claimPeriod := types.NewClaimPeriod(denom, id, end, timeLock)
|
||||||
|
k.SetClaimPeriod(ctx, claimPeriod)
|
||||||
|
k.SetNextClaimPeriodID(ctx, denom, id+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToClaim adds the amount to an existing claim or creates a new one for that amount
|
||||||
|
func (k Keeper) AddToClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64, amount sdk.Coin) {
|
||||||
|
claim, found := k.GetClaim(ctx, addr, denom, id)
|
||||||
|
if found {
|
||||||
|
claim.Reward = claim.Reward.Add(amount)
|
||||||
|
} else {
|
||||||
|
claim = types.NewClaim(addr, amount, denom, id)
|
||||||
|
}
|
||||||
|
k.SetClaim(ctx, claim)
|
||||||
|
}
|
164
x/incentive/module.go
Normal file
164
x/incentive/module.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package incentive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
sim "github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/client/cli"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/client/rest"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/simulation"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ module.AppModule = AppModule{}
|
||||||
|
_ module.AppModuleBasic = AppModuleBasic{}
|
||||||
|
_ module.AppModuleSimulation = AppModule{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppModuleBasic defines the basic application module used by the incentive module.
|
||||||
|
type AppModuleBasic struct{}
|
||||||
|
|
||||||
|
// Name returns the incentive module's name.
|
||||||
|
func (AppModuleBasic) Name() string {
|
||||||
|
return types.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCodec registers the incentive module's types for the given codec.
|
||||||
|
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
|
||||||
|
types.RegisterCodec(cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGenesis returns default genesis state as raw bytes for the incentive
|
||||||
|
// module.
|
||||||
|
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
|
||||||
|
return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateGenesis performs genesis state validation for the incentive module.
|
||||||
|
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
||||||
|
var gs types.GenesisState
|
||||||
|
err := types.ModuleCdc.UnmarshalJSON(bz, &gs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gs.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRESTRoutes registers the REST routes for the incentive module.
|
||||||
|
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
|
||||||
|
rest.RegisterRoutes(ctx, rtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxCmd returns the root tx command for the incentive module.
|
||||||
|
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
|
return cli.GetTxCmd(cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryCmd returns no root query command for the crisis module.
|
||||||
|
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
|
return cli.GetQueryCmd(types.StoreKey, cdc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterStoreDecoder registers a decoder for cdp module's types
|
||||||
|
func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
|
||||||
|
sdr[StoreKey] = simulation.DecodeStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateGenesisState creates a randomized GenState of the cdp module
|
||||||
|
func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) {
|
||||||
|
simulation.RandomizedGenState(simState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomizedParams creates randomized cdp param changes for the simulator.
|
||||||
|
func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange {
|
||||||
|
return simulation.ParamChanges(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProposalContents doesn't return any content functions for governance proposals.
|
||||||
|
func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeightedOperations returns the all the bep3 module operations with their respective weights.
|
||||||
|
func (am AppModule) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppModule implements the sdk.AppModule interface.
|
||||||
|
type AppModule struct {
|
||||||
|
AppModuleBasic
|
||||||
|
|
||||||
|
keeper keeper.Keeper
|
||||||
|
supplyKeeper types.SupplyKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppModule creates a new AppModule object
|
||||||
|
func NewAppModule(keeper keeper.Keeper, supplyKeeper types.SupplyKeeper) AppModule {
|
||||||
|
return AppModule{
|
||||||
|
AppModuleBasic: AppModuleBasic{},
|
||||||
|
keeper: keeper,
|
||||||
|
supplyKeeper: supplyKeeper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the incentive module's name.
|
||||||
|
func (AppModule) Name() string {
|
||||||
|
return types.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInvariants registers the incentive module invariants.
|
||||||
|
func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||||
|
|
||||||
|
// Route returns the message routing key for the incentive module.
|
||||||
|
func (AppModule) Route() string {
|
||||||
|
return types.RouterKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler returns an sdk.Handler for the incentive module.
|
||||||
|
func (am AppModule) NewHandler() sdk.Handler {
|
||||||
|
return NewHandler(am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuerierRoute returns the incentive module's querier route name.
|
||||||
|
func (AppModule) QuerierRoute() string {
|
||||||
|
return types.QuerierRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuerierHandler returns the incentive module sdk.Querier.
|
||||||
|
func (am AppModule) NewQuerierHandler() sdk.Querier {
|
||||||
|
return keeper.NewQuerier(am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitGenesis performs genesis initialization for the incentive module. It returns no validator updates.
|
||||||
|
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
|
||||||
|
var gs types.GenesisState
|
||||||
|
types.ModuleCdc.MustUnmarshalJSON(data, &gs)
|
||||||
|
InitGenesis(ctx, am.keeper, am.supplyKeeper, gs)
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportGenesis returns the exported genesis state as raw bytes for the incentive module
|
||||||
|
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
|
||||||
|
gs := ExportGenesis(ctx, am.keeper)
|
||||||
|
return types.ModuleCdc.MustMarshalJSON(gs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginBlock returns the begin blocker for the incentive module.
|
||||||
|
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
||||||
|
BeginBlocker(ctx, am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndBlock returns the end blocker for the incentive module. It returns no validator updates.
|
||||||
|
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||||
|
return []abci.ValidatorUpdate{}
|
||||||
|
}
|
12
x/incentive/simulation/decoder.go
Normal file
12
x/incentive/simulation/decoder.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/tendermint/tendermint/libs/kv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeStore unmarshals the KVPair's Value to the corresponding incentive type
|
||||||
|
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||||
|
// TODO implement this
|
||||||
|
return ""
|
||||||
|
}
|
14
x/incentive/simulation/params.go
Normal file
14
x/incentive/simulation/params.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParamChanges defines the parameters that can be modified by param change proposals
|
||||||
|
// on the simulation
|
||||||
|
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
||||||
|
// TODO implement this
|
||||||
|
return []simulation.ParamChange{}
|
||||||
|
}
|
22
x/incentive/simulation/simulation.go
Normal file
22
x/incentive/simulation/simulation.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomizedGenState generates a random GenesisState for cdp
|
||||||
|
func RandomizedGenState(simState *module.SimulationState) {
|
||||||
|
|
||||||
|
// TODO implement this fully
|
||||||
|
// - randomly generating the genesis params
|
||||||
|
// - overwriting with genesis provided to simulation
|
||||||
|
genesis := types.DefaultGenesisState()
|
||||||
|
|
||||||
|
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis))
|
||||||
|
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis)
|
||||||
|
}
|
14
x/incentive/types/account.go
Normal file
14
x/incentive/types/account.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTotalVestingPeriodLength returns the summed length of all vesting periods
|
||||||
|
func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
|
||||||
|
length := int64(0)
|
||||||
|
for _, period := range periods {
|
||||||
|
length += period.Length
|
||||||
|
}
|
||||||
|
return length
|
||||||
|
}
|
51
x/incentive/types/account_test.go
Normal file
51
x/incentive/types/account_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accountTest struct {
|
||||||
|
periods vesting.Periods
|
||||||
|
expectedVal int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
tests []accountTest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccountTestSuite) SetupTest() {
|
||||||
|
tests := []accountTest{
|
||||||
|
accountTest{
|
||||||
|
periods: vesting.Periods{
|
||||||
|
vesting.Period{
|
||||||
|
Length: int64(100),
|
||||||
|
Amount: sdk.Coins{},
|
||||||
|
},
|
||||||
|
vesting.Period{
|
||||||
|
Length: int64(200),
|
||||||
|
Amount: sdk.Coins{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVal: int64(300),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.tests = tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccountTestSuite) TestGetTotalPeriodLength() {
|
||||||
|
for _, t := range suite.tests {
|
||||||
|
length := types.GetTotalVestingPeriodLength(t.periods)
|
||||||
|
suite.Equal(t.expectedVal, length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(AccountTestSuite))
|
||||||
|
}
|
22
x/incentive/types/codec.go
Normal file
22
x/incentive/types/codec.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
|
// ModuleCdc generic sealed codec to be used throughout module
|
||||||
|
var ModuleCdc *codec.Codec
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cdc := codec.New()
|
||||||
|
RegisterCodec(cdc)
|
||||||
|
codec.RegisterCrypto(cdc)
|
||||||
|
ModuleCdc = cdc.Seal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCodec registers the necessary types for incentive module
|
||||||
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
|
cdc.RegisterConcrete(MsgClaimReward{}, "incentive/MsgClaimReward", nil)
|
||||||
|
cdc.RegisterConcrete(GenesisClaimPeriodID{}, "incentive/GenesisClaimPeriodID", nil)
|
||||||
|
cdc.RegisterConcrete(RewardPeriod{}, "incentive/RewardPeriod", nil)
|
||||||
|
cdc.RegisterConcrete(ClaimPeriod{}, "incentive/ClaimPeriod", nil)
|
||||||
|
cdc.RegisterConcrete(Claim{}, "incentive/Claim", nil)
|
||||||
|
}
|
15
x/incentive/types/errors.go
Normal file
15
x/incentive/types/errors.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DONTCOVER
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrClaimNotFound = sdkerrors.Register(ModuleName, 1, "no claim with input id found for owner and denom")
|
||||||
|
ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 2, "no claim period found for id and denom")
|
||||||
|
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 3, "account type not supported")
|
||||||
|
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 4, "no claims with denom found for address")
|
||||||
|
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 5, "module account has insufficient balance to pay claim")
|
||||||
|
)
|
8
x/incentive/types/events.go
Normal file
8
x/incentive/types/events.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventTypeClaim = "claim_reward"
|
||||||
|
|
||||||
|
AttributeValueCategory = ModuleName
|
||||||
|
AttributeKeySender = "sender"
|
||||||
|
)
|
27
x/incentive/types/expected_keepers.go
Normal file
27
x/incentive/types/expected_keepers.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||||
|
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||||
|
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SupplyKeeper defines the expected supply keeper for module accounts
|
||||||
|
type SupplyKeeper interface {
|
||||||
|
GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI
|
||||||
|
|
||||||
|
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CdpKeeper defines the expected cdp keeper for interacting with cdps
|
||||||
|
type CdpKeeper interface {
|
||||||
|
IterateCdpsByDenom(ctx sdk.Context, denom string, cb func(cdp cdptypes.CDP) (stop bool))
|
||||||
|
GetTotalPrincipal(ctx sdk.Context, collateralDenom string, principalDenom string) (total sdk.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountKeeper defines the expected keeper interface for interacting with account
|
||||||
|
type AccountKeeper interface {
|
||||||
|
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||||
|
SetAccount(ctx sdk.Context, acc authexported.Account)
|
||||||
|
}
|
75
x/incentive/types/genesis.go
Normal file
75
x/incentive/types/genesis.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenesisClaimPeriodID stores the next claim id and its corresponding denom
|
||||||
|
type GenesisClaimPeriodID struct {
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
ID uint64 `json:"id" yaml:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenesisClaimPeriodIDs array of GenesisClaimPeriodID
|
||||||
|
type GenesisClaimPeriodIDs []GenesisClaimPeriodID
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenesisState returns a new genesis state
|
||||||
|
func NewGenesisState(params Params, previousBlockTime time.Time, rp RewardPeriods, cp ClaimPeriods, c Claims, ids GenesisClaimPeriodIDs) GenesisState {
|
||||||
|
return GenesisState{
|
||||||
|
Params: params,
|
||||||
|
PreviousBlockTime: previousBlockTime,
|
||||||
|
RewardPeriods: rp,
|
||||||
|
ClaimPeriods: cp,
|
||||||
|
Claims: c,
|
||||||
|
NextClaimPeriodIDs: ids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGenesisState returns a default genesis state
|
||||||
|
func DefaultGenesisState() GenesisState {
|
||||||
|
return GenesisState{
|
||||||
|
Params: DefaultParams(),
|
||||||
|
PreviousBlockTime: DefaultPreviousBlockTime,
|
||||||
|
RewardPeriods: RewardPeriods{},
|
||||||
|
ClaimPeriods: ClaimPeriods{},
|
||||||
|
Claims: Claims{},
|
||||||
|
NextClaimPeriodIDs: GenesisClaimPeriodIDs{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs basic validation of genesis data returning an
|
||||||
|
// error for any failed validation criteria.
|
||||||
|
func (gs GenesisState) Validate() error {
|
||||||
|
|
||||||
|
if err := gs.Params.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if gs.PreviousBlockTime.Equal(time.Time{}) {
|
||||||
|
return fmt.Errorf("previous block time not set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal checks whether two gov GenesisState structs are equivalent
|
||||||
|
func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
||||||
|
b1 := ModuleCdc.MustMarshalBinaryBare(gs)
|
||||||
|
b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
|
||||||
|
return bytes.Equal(b1, b2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if a GenesisState is empty
|
||||||
|
func (gs GenesisState) IsEmpty() bool {
|
||||||
|
return gs.Equal(GenesisState{})
|
||||||
|
}
|
61
x/incentive/types/keys.go
Normal file
61
x/incentive/types/keys.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ModuleName The name that will be used throughout the module
|
||||||
|
ModuleName = "incentive"
|
||||||
|
|
||||||
|
// StoreKey Top level store key where all module items will be stored
|
||||||
|
StoreKey = ModuleName
|
||||||
|
|
||||||
|
// RouterKey Top level router key
|
||||||
|
RouterKey = ModuleName
|
||||||
|
|
||||||
|
// DefaultParamspace default name for parameter store
|
||||||
|
DefaultParamspace = ModuleName
|
||||||
|
|
||||||
|
// QuerierRoute route used for abci queries
|
||||||
|
QuerierRoute = ModuleName
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key Prefixes
|
||||||
|
var (
|
||||||
|
RewardPeriodKeyPrefix = []byte{0x01} // prefix for keys that store reward periods
|
||||||
|
ClaimPeriodKeyPrefix = []byte{0x02} // prefix for keys that store claim periods
|
||||||
|
ClaimKeyPrefix = []byte{0x03} // prefix for keys that store claims
|
||||||
|
NextClaimPeriodIDPrefix = []byte{0x04} // prefix for keys that store the next ID for claims periods
|
||||||
|
PreviousBlockTimeKey = []byte{0x05} // prefix for key that stores the previous blocktime
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keys
|
||||||
|
// 0x00:Denom <> RewardPeriod the current active reward period (max 1 reward period per denom)
|
||||||
|
// 0x01:Denom:ID <> ClaimPeriod object for that ID, indexed by denom and ID
|
||||||
|
// 0x02:Denom:ID:Owner <> Claim object, indexed by Denom, ID and owner
|
||||||
|
// 0x03:Denom <> NextClaimPeriodIDPrefix the ID of the next claim period, indexed by denom
|
||||||
|
|
||||||
|
// BytesToUint64 returns uint64 format from a byte array
|
||||||
|
func BytesToUint64(bz []byte) uint64 {
|
||||||
|
return binary.BigEndian.Uint64(bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaimPeriodPrefix returns the key (denom + id) for a claim prefix
|
||||||
|
func GetClaimPeriodPrefix(denom string, id uint64) []byte {
|
||||||
|
return createKey([]byte(denom), sdk.Uint64ToBigEndian(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaimPrefix returns the key (denom + id + address) for a claim
|
||||||
|
func GetClaimPrefix(addr sdk.AccAddress, denom string, id uint64) []byte {
|
||||||
|
return createKey([]byte(denom), sdk.Uint64ToBigEndian(id), addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createKey(bytes ...[]byte) (r []byte) {
|
||||||
|
for _, b := range bytes {
|
||||||
|
r = append(r, b...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
54
x/incentive/types/msg.go
Normal file
54
x/incentive/types/msg.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensure Msg interface compliance at compile time
|
||||||
|
var _ sdk.Msg = &MsgClaimReward{}
|
||||||
|
|
||||||
|
// MsgClaimReward message type used to claim rewards
|
||||||
|
type MsgClaimReward struct {
|
||||||
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgClaimReward returns a new MsgClaimReward.
|
||||||
|
func NewMsgClaimReward(sender sdk.AccAddress, denom string) MsgClaimReward {
|
||||||
|
return MsgClaimReward{
|
||||||
|
Sender: sender,
|
||||||
|
Denom: denom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route return the message type used for routing the message.
|
||||||
|
func (msg MsgClaimReward) Route() string { return RouterKey }
|
||||||
|
|
||||||
|
// Type returns a human-readable string for the message, intended for utilization within tags.
|
||||||
|
func (msg MsgClaimReward) Type() string { return "claim_reward" }
|
||||||
|
|
||||||
|
// ValidateBasic does a simple validation check that doesn't require access to state.
|
||||||
|
func (msg MsgClaimReward) ValidateBasic() error {
|
||||||
|
if msg.Sender.Empty() {
|
||||||
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(msg.Denom) == "" {
|
||||||
|
return errors.New("invalid (empty) denom")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignBytes gets the canonical byte representation of the Msg.
|
||||||
|
func (msg MsgClaimReward) GetSignBytes() []byte {
|
||||||
|
bz := ModuleCdc.MustMarshalJSON(msg)
|
||||||
|
return sdk.MustSortJSON(bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigners returns the addresses of signers that must sign.
|
||||||
|
func (msg MsgClaimReward) GetSigners() []sdk.AccAddress {
|
||||||
|
return []sdk.AccAddress{msg.Sender}
|
||||||
|
}
|
59
x/incentive/types/msg_test.go
Normal file
59
x/incentive/types/msg_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgTest struct {
|
||||||
|
from sdk.AccAddress
|
||||||
|
denom string
|
||||||
|
expectPass bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MsgTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
tests []msgTest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) SetupTest() {
|
||||||
|
tests := []msgTest{
|
||||||
|
msgTest{
|
||||||
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
|
denom: "bnb",
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
msgTest{
|
||||||
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
|
denom: "",
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
msgTest{
|
||||||
|
from: sdk.AccAddress{},
|
||||||
|
denom: "bnb",
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.tests = tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MsgTestSuite) TestMsgValidation() {
|
||||||
|
for _, t := range suite.tests {
|
||||||
|
msg := types.NewMsgClaimReward(t.from, t.denom)
|
||||||
|
err := msg.ValidateBasic()
|
||||||
|
if t.expectPass {
|
||||||
|
suite.NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(MsgTestSuite))
|
||||||
|
}
|
165
x/incentive/types/params.go
Normal file
165
x/incentive/types/params.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
|
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||||
|
kavadistTypes "github.com/kava-labs/kava/x/kavadist/types"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parameter keys and default values
|
||||||
|
var (
|
||||||
|
KeyActive = []byte("Active")
|
||||||
|
KeyRewards = []byte("Rewards")
|
||||||
|
DefaultActive = false
|
||||||
|
DefaultRewards = Rewards{}
|
||||||
|
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||||
|
GovDenom = cdptypes.DefaultGovDenom
|
||||||
|
PrincipalDenom = "usdx"
|
||||||
|
IncentiveMacc = kavadistTypes.ModuleName
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParams returns a new params object
|
||||||
|
func NewParams(active bool, rewards Rewards) Params {
|
||||||
|
return Params{
|
||||||
|
Active: active,
|
||||||
|
Rewards: rewards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultParams returns default params for kavadist module
|
||||||
|
func DefaultParams() Params {
|
||||||
|
return NewParams(DefaultActive, DefaultRewards)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (p Params) String() string {
|
||||||
|
return fmt.Sprintf(`Params:
|
||||||
|
Active: %t
|
||||||
|
Rewards: %s`, p.Active, p.Rewards)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamKeyTable Key declaration for parameters
|
||||||
|
func ParamKeyTable() params.KeyTable {
|
||||||
|
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||||
|
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||||
|
return params.ParamSetPairs{
|
||||||
|
params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam),
|
||||||
|
params.NewParamSetPair(KeyRewards, &p.Rewards, validateRewardsParam),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks that the parameters have valid values.
|
||||||
|
func (p Params) Validate() error {
|
||||||
|
if err := validateActiveParam(p.Active); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateRewardsParam(p.Rewards); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateActiveParam(i interface{}) error {
|
||||||
|
_, ok := i.(bool)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRewardsParam(i interface{}) error {
|
||||||
|
rewards, ok := i.(Rewards)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
|
}
|
||||||
|
rewardDenoms := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, reward := range rewards {
|
||||||
|
if strings.TrimSpace(reward.Denom) == "" {
|
||||||
|
return fmt.Errorf("cannot have empty reward denom: %s", reward)
|
||||||
|
}
|
||||||
|
if rewardDenoms[reward.Denom] {
|
||||||
|
return fmt.Errorf("cannot have duplicate reward denoms: %s", reward.Denom)
|
||||||
|
}
|
||||||
|
rewardDenoms[reward.Denom] = true
|
||||||
|
if !reward.AvailableRewards.IsValid() {
|
||||||
|
return fmt.Errorf("invalid reward coins %s for %s", reward.AvailableRewards, reward.Denom)
|
||||||
|
}
|
||||||
|
if !reward.AvailableRewards.IsPositive() {
|
||||||
|
return fmt.Errorf("reward amount must be positive, is %s for %s", reward.AvailableRewards, reward.Denom)
|
||||||
|
}
|
||||||
|
if int(reward.Duration.Seconds()) <= 0 {
|
||||||
|
return fmt.Errorf("reward duration must be positive, is %s for %s", reward.Duration.String(), reward.Denom)
|
||||||
|
}
|
||||||
|
if int(reward.TimeLock.Seconds()) < 0 {
|
||||||
|
return fmt.Errorf("reward timelock must be non-negative, is %s for %s", reward.TimeLock.String(), reward.Denom)
|
||||||
|
}
|
||||||
|
if int(reward.ClaimDuration.Seconds()) <= 0 {
|
||||||
|
return fmt.Errorf("reward timelock must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReward returns a new Reward
|
||||||
|
func NewReward(active bool, denom string, reward sdk.Coin, duration time.Duration, timelock time.Duration, claimDuration time.Duration) Reward {
|
||||||
|
return Reward{
|
||||||
|
Active: active,
|
||||||
|
Denom: denom,
|
||||||
|
AvailableRewards: reward,
|
||||||
|
Duration: duration,
|
||||||
|
TimeLock: timelock,
|
||||||
|
ClaimDuration: claimDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (r Reward) String() string {
|
||||||
|
return fmt.Sprintf(`Reward:
|
||||||
|
Active: %t,
|
||||||
|
Denom: %s,
|
||||||
|
Available Rewards: %s,
|
||||||
|
Duration: %s,
|
||||||
|
Time Lock: %s,
|
||||||
|
Claim Duration: %s`,
|
||||||
|
r.Active, r.Denom, r.AvailableRewards, r.Duration, r.TimeLock, r.ClaimDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewards array of Reward
|
||||||
|
type Rewards []Reward
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (rs Rewards) String() string {
|
||||||
|
out := "Rewards\n"
|
||||||
|
for _, r := range rs {
|
||||||
|
out += fmt.Sprintf("%s\n", r)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
152
x/incentive/types/params_test.go
Normal file
152
x/incentive/types/params_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type paramTest struct {
|
||||||
|
params types.Params
|
||||||
|
expectPass bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParamTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
tests []paramTest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamTestSuite) SetupTest() {
|
||||||
|
p1 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p2 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
|
},
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p3 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * -24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p4 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * -8766,
|
||||||
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p5 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p6 := types.Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: types.Rewards{
|
||||||
|
types.Reward{
|
||||||
|
Active: true,
|
||||||
|
Denom: "bnb",
|
||||||
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
|
||||||
|
Duration: time.Hour * 24 * 7,
|
||||||
|
TimeLock: time.Hour * 8766,
|
||||||
|
ClaimDuration: time.Hour * 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.tests = []paramTest{
|
||||||
|
paramTest{
|
||||||
|
params: p1,
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
paramTest{
|
||||||
|
params: p2,
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
paramTest{
|
||||||
|
params: p3,
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
paramTest{
|
||||||
|
params: p4,
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
paramTest{
|
||||||
|
params: p5,
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
paramTest{
|
||||||
|
params: p6,
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamTestSuite) TestParamValidation() {
|
||||||
|
for _, t := range suite.tests {
|
||||||
|
err := t.params.Validate()
|
||||||
|
if t.expectPass {
|
||||||
|
suite.NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ParamTestSuite))
|
||||||
|
}
|
11
x/incentive/types/period.go
Normal file
11
x/incentive/types/period.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPeriod returns a new vesting period
|
||||||
|
func NewPeriod(amount sdk.Coins, length int64) vesting.Period {
|
||||||
|
return vesting.Period{Amount: amount, Length: length}
|
||||||
|
}
|
35
x/incentive/types/querier.go
Normal file
35
x/incentive/types/querier.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Querier routes for the incentive module
|
||||||
|
const (
|
||||||
|
QueryGetClaims = "claims"
|
||||||
|
RestClaimOwner = "owner"
|
||||||
|
RestClaimDenom = "denom"
|
||||||
|
QueryGetParams = "parameters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryClaimsParams params for query /incentive/claims
|
||||||
|
type QueryClaimsParams struct {
|
||||||
|
Owner sdk.AccAddress
|
||||||
|
Denom string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryClaimsParams returns QueryClaimsParams
|
||||||
|
func NewQueryClaimsParams(owner sdk.AccAddress, denom string) QueryClaimsParams {
|
||||||
|
return QueryClaimsParams{
|
||||||
|
Owner: owner,
|
||||||
|
Denom: denom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostClaimReq defines the properties of claim transaction's request body.
|
||||||
|
type PostClaimReq struct {
|
||||||
|
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||||
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
}
|
97
x/incentive/types/rewards.go
Normal file
97
x/incentive/types/rewards.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RewardPeriod stores the state of an ongoing reward
|
||||||
|
type RewardPeriod struct {
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (rp RewardPeriod) String() string {
|
||||||
|
return fmt.Sprintf(`Reward Period:
|
||||||
|
Denom: %s,
|
||||||
|
Start: %s,
|
||||||
|
End: %s,
|
||||||
|
Reward: %s,
|
||||||
|
Claim End: %s,
|
||||||
|
Claim Time Lock: %s`,
|
||||||
|
rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRewardPeriod returns a new RewardPeriod
|
||||||
|
func NewRewardPeriod(denom string, start time.Time, end time.Time, reward sdk.Coin, claimEnd time.Time, claimTimeLock time.Duration) RewardPeriod {
|
||||||
|
return RewardPeriod{
|
||||||
|
Denom: denom,
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
Reward: reward,
|
||||||
|
ClaimEnd: claimEnd,
|
||||||
|
ClaimTimeLock: claimTimeLock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewardPeriods array of RewardPeriod
|
||||||
|
type RewardPeriods []RewardPeriod
|
||||||
|
|
||||||
|
// ClaimPeriod stores the state of an ongoing claim period
|
||||||
|
type ClaimPeriod struct {
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
ID uint64 `json:"id" yaml:"id"`
|
||||||
|
End time.Time `json:"end" yaml:"end"`
|
||||||
|
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClaimPeriod returns a new ClaimPeriod
|
||||||
|
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod {
|
||||||
|
return ClaimPeriod{
|
||||||
|
Denom: denom,
|
||||||
|
ID: id,
|
||||||
|
End: end,
|
||||||
|
TimeLock: timeLock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimPeriods array of ClaimPeriod
|
||||||
|
type ClaimPeriods []ClaimPeriod
|
||||||
|
|
||||||
|
// Claim stores the rewards that can be claimed by owner
|
||||||
|
type Claim struct {
|
||||||
|
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||||
|
Reward sdk.Coin `json:"reward" yaml:"reward"`
|
||||||
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
|
ClaimPeriodID uint64 `json:"claim_period_id" yaml:"claim_period_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClaim returns a new Claim
|
||||||
|
func NewClaim(owner sdk.AccAddress, reward sdk.Coin, denom string, claimPeriodID uint64) Claim {
|
||||||
|
return Claim{
|
||||||
|
Owner: owner,
|
||||||
|
Reward: reward,
|
||||||
|
Denom: denom,
|
||||||
|
ClaimPeriodID: claimPeriodID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (c Claim) String() string {
|
||||||
|
return fmt.Sprintf(`Claim:
|
||||||
|
Owner: %s,
|
||||||
|
Denom: %s,
|
||||||
|
Reward: %s,
|
||||||
|
Claim Period ID: %d,`,
|
||||||
|
c.Owner, c.Denom, c.Reward, c.ClaimPeriodID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims array of Claim
|
||||||
|
type Claims []Claim
|
Loading…
Reference in New Issue
Block a user