mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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/bep3" | ||||
| 	"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/pricefeed" | ||||
| 	validatorvesting "github.com/kava-labs/kava/x/validator-vesting" | ||||
| @ -69,6 +70,7 @@ var ( | ||||
| 		pricefeed.AppModuleBasic{}, | ||||
| 		bep3.AppModuleBasic{}, | ||||
| 		kavadist.AppModuleBasic{}, | ||||
| 		incentive.AppModuleBasic{}, | ||||
| 	) | ||||
| 
 | ||||
| 	// module account permissions
 | ||||
| @ -121,6 +123,7 @@ type App struct { | ||||
| 	pricefeedKeeper pricefeed.Keeper | ||||
| 	bep3Keeper      bep3.Keeper | ||||
| 	kavadistKeeper  kavadist.Keeper | ||||
| 	incentiveKeeper incentive.Keeper | ||||
| 
 | ||||
| 	// the 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, | ||||
| 		gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey, | ||||
| 		auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey, | ||||
| 		kavadist.StoreKey, | ||||
| 		kavadist.StoreKey, incentive.StoreKey, | ||||
| 	) | ||||
| 	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) | ||||
| 	bep3Subspace := app.paramsKeeper.Subspace(bep3.DefaultParamspace) | ||||
| 	kavadistSubspace := app.paramsKeeper.Subspace(kavadist.DefaultParamspace) | ||||
| 	incentiveSubspace := app.paramsKeeper.Subspace(incentive.DefaultParamspace) | ||||
| 
 | ||||
| 	// add keepers
 | ||||
| 	app.accountKeeper = auth.NewAccountKeeper( | ||||
| @ -295,6 +299,14 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, | ||||
| 		kavadistSubspace, | ||||
| 		app.supplyKeeper, | ||||
| 	) | ||||
| 	app.incentiveKeeper = incentive.NewKeeper( | ||||
| 		app.cdc, | ||||
| 		keys[incentive.StoreKey], | ||||
| 		incentiveSubspace, | ||||
| 		app.supplyKeeper, | ||||
| 		app.cdpKeeper, | ||||
| 		app.accountKeeper, | ||||
| 	) | ||||
| 
 | ||||
| 	// register the staking 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), | ||||
| 		bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), | ||||
| 		kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), | ||||
| 		incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), | ||||
| 	) | ||||
| 
 | ||||
| 	// 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
 | ||||
| 	// 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) | ||||
| 
 | ||||
| @ -335,7 +348,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, | ||||
| 		validatorvesting.ModuleName, distr.ModuleName, | ||||
| 		staking.ModuleName, bank.ModuleName, slashing.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
 | ||||
| 		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.
 | ||||
| @ -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), | ||||
| 		bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), | ||||
| 		kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), | ||||
| 		incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), | ||||
| 	) | ||||
| 
 | ||||
| 	app.sm.RegisterStoreDecoders() | ||||
|  | ||||
| @ -30,6 +30,7 @@ import ( | ||||
| 	"github.com/kava-labs/kava/x/auction" | ||||
| 	"github.com/kava-labs/kava/x/bep3" | ||||
| 	"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/pricefeed" | ||||
| 	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) GetBep3Keeper() bep3.Keeper           { return tApp.bep3Keeper } | ||||
| 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
 | ||||
| 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.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI= | ||||
| 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.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ= | ||||
| 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()) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		swapID, err := types.HexToBytes(vars[restSwapID]) | ||||
| 		if err != nil { | ||||
| 			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
	 Kevin Davis
						Kevin Davis