diff --git a/app/app.go b/app/app.go index 9594c1ce..3d1984ab 100644 --- a/app/app.go +++ b/app/app.go @@ -358,6 +358,16 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio bep3Subspace, app.ModuleAccountAddrs(), ) + hardKeeper := hard.NewKeeper( + app.cdc, + keys[hard.StoreKey], + hardSubspace, + app.accountKeeper, + app.supplyKeeper, + &stakingKeeper, + app.pricefeedKeeper, + app.auctionKeeper, + ) app.kavadistKeeper = kavadist.NewKeeper( app.cdc, keys[kavadist.StoreKey], @@ -370,6 +380,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio incentiveSubspace, app.supplyKeeper, &cdpKeeper, + &hardKeeper, app.accountKeeper, ) app.issuanceKeeper = issuance.NewKeeper( @@ -379,16 +390,6 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio app.accountKeeper, app.supplyKeeper, ) - app.hardKeeper = hard.NewKeeper( - app.cdc, - keys[hard.StoreKey], - hardSubspace, - app.accountKeeper, - app.supplyKeeper, - &stakingKeeper, - app.pricefeedKeeper, - app.auctionKeeper, - ) // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks @@ -397,6 +398,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio app.cdpKeeper = *cdpKeeper.SetHooks(cdp.NewMultiCDPHooks(app.incentiveKeeper.Hooks())) + app.hardKeeper = *hardKeeper.SetHooks(hard.NewMultiHARDHooks(app.incentiveKeeper.Hooks())) + // create the module manager (Note: Any module instantiated in the module manager that is later modified // must be passed by reference here.) app.mm = module.NewManager( @@ -432,7 +435,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio app.mm.SetOrderBeginBlockers( upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, auction.ModuleName, cdp.ModuleName, - bep3.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.ModuleName, hard.ModuleName, + bep3.ModuleName, hard.ModuleName, committee.ModuleName, issuance.ModuleName, incentive.ModuleName, ) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) @@ -442,8 +445,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio 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, incentive.ModuleName, committee.ModuleName, issuance.ModuleName, hard.ModuleName, + pricefeed.ModuleName, cdp.ModuleName, hard.ModuleName, auction.ModuleName, + bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.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. diff --git a/x/hard/abci.go b/x/hard/abci.go index e1a4f519..ac4801b4 100644 --- a/x/hard/abci.go +++ b/x/hard/abci.go @@ -4,13 +4,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// BeginBlocker applies rewards to liquidity providers and delegators according to params +// BeginBlocker updates interest rates and attempts liquidations func BeginBlocker(ctx sdk.Context, k Keeper) { - k.ApplyDepositRewards(ctx) - if k.ShouldDistributeValidatorRewards(ctx, k.BondDenom(ctx)) { - k.ApplyDelegationRewards(ctx, k.BondDenom(ctx)) - k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), k.BondDenom(ctx)) - } k.ApplyInterestRateUpdates(ctx) k.AttemptIndexLiquidations(ctx) k.SetPreviousBlockTime(ctx, ctx.BlockTime()) diff --git a/x/hard/alias.go b/x/hard/alias.go index e27ddf16..e083c9a6 100644 --- a/x/hard/alias.go +++ b/x/hard/alias.go @@ -8,9 +8,6 @@ import ( ) const ( - BeginningOfMonth = keeper.BeginningOfMonth - MidMonth = keeper.MidMonth - PaymentHour = keeper.PaymentHour AttributeKeyBlockHeight = types.AttributeKeyBlockHeight AttributeKeyClaimAmount = types.AttributeKeyClaimAmount AttributeKeyClaimHolder = types.AttributeKeyClaimHolder @@ -49,91 +46,77 @@ const ( var ( // function aliases - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - CalculateUtilizationRatio = keeper.CalculateUtilizationRatio - CalculateBorrowRate = keeper.CalculateBorrowRate - CalculateBorrowInterestFactor = keeper.CalculateBorrowInterestFactor - CalculateSupplyInterestFactor = keeper.CalculateSupplyInterestFactor - APYToSPY = keeper.APYToSPY - ClaimKey = types.ClaimKey - DefaultGenesisState = types.DefaultGenesisState - DefaultParams = types.DefaultParams - DepositTypeIteratorKey = types.DepositTypeIteratorKey - GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength - NewClaim = types.NewClaim - NewDelegatorDistributionSchedule = types.NewDelegatorDistributionSchedule - NewDeposit = types.NewDeposit - NewDistributionSchedule = types.NewDistributionSchedule - NewGenesisState = types.NewGenesisState - NewMsgClaimReward = types.NewMsgClaimReward - NewMsgDeposit = types.NewMsgDeposit - NewMsgWithdraw = types.NewMsgWithdraw - NewMultiplier = types.NewMultiplier - NewParams = types.NewParams - NewPeriod = types.NewPeriod - NewQueryAccountParams = types.NewQueryAccountParams - NewQueryClaimParams = types.NewQueryClaimParams - ParamKeyTable = types.ParamKeyTable - RegisterCodec = types.RegisterCodec + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + CalculateUtilizationRatio = keeper.CalculateUtilizationRatio + CalculateBorrowRate = keeper.CalculateBorrowRate + CalculateBorrowInterestFactor = keeper.CalculateBorrowInterestFactor + CalculateSupplyInterestFactor = keeper.CalculateSupplyInterestFactor + APYToSPY = keeper.APYToSPY + DefaultGenesisState = types.DefaultGenesisState + DefaultParams = types.DefaultParams + DepositTypeIteratorKey = types.DepositTypeIteratorKey + GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength + NewBorrowLimit = types.NewBorrowLimit + NewInterestRateModel = types.NewInterestRateModel + NewDeposit = types.NewDeposit + NewGenesisState = types.NewGenesisState + NewMsgClaimReward = types.NewMsgClaimReward + NewMsgDeposit = types.NewMsgDeposit + NewMsgWithdraw = types.NewMsgWithdraw + NewMultiHARDHooks = types.NewMultiHARDHooks + NewMultiplier = types.NewMultiplier + NewParams = types.NewParams + NewPeriod = types.NewPeriod + NewMoneyMarket = types.NewMoneyMarket + NewQueryAccountParams = types.NewQueryAccountParams + NewQueryClaimParams = types.NewQueryClaimParams + ParamKeyTable = types.ParamKeyTable + RegisterCodec = types.RegisterCodec // variable aliases - BorrowsKeyPrefix = types.BorrowsKeyPrefix - ClaimsKeyPrefix = types.ClaimsKeyPrefix - DefaultActive = types.DefaultActive - DefaultDelegatorSchedules = types.DefaultDelegatorSchedules - DefaultDistributionTimes = types.DefaultDistributionTimes - DefaultGovSchedules = types.DefaultGovSchedules - DefaultLPSchedules = types.DefaultLPSchedules - DefaultPreviousBlockTime = types.DefaultPreviousBlockTime - ClaimTypesClaimQuery = types.ClaimTypesClaimQuery - DepositsKeyPrefix = types.DepositsKeyPrefix - ErrAccountNotFound = types.ErrAccountNotFound - ErrClaimExpired = types.ErrClaimExpired - ErrClaimNotFound = types.ErrClaimNotFound - ErrDepositNotFound = types.ErrDepositNotFound - ErrGovScheduleNotFound = types.ErrGovScheduleNotFound - ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance - ErrInvaliWithdrawAmount = types.ErrInvalidWithdrawAmount - ErrInvalidAccountType = types.ErrInvalidAccountType - ErrInvalidDepositDenom = types.ErrInvalidDepositDenom - ErrInvalidClaimType = types.ErrInvalidClaimType - ErrInvalidMultiplier = types.ErrInvalidMultiplier - ErrLPScheduleNotFound = types.ErrLPScheduleNotFound - ErrZeroClaim = types.ErrZeroClaim - GovDenom = types.GovDenom - KeyActive = types.KeyActive - KeyDelegatorSchedule = types.KeyDelegatorSchedule - KeyLPSchedules = types.KeyLPSchedules - ModuleCdc = types.ModuleCdc - PreviousBlockTimeKey = types.PreviousBlockTimeKey - PreviousDelegationDistributionKey = types.PreviousDelegationDistributionKey + BorrowsKeyPrefix = types.BorrowsKeyPrefix + DefaultActive = types.DefaultActive + DefaultPreviousBlockTime = types.DefaultPreviousBlockTime + ClaimTypesClaimQuery = types.ClaimTypesClaimQuery + DepositsKeyPrefix = types.DepositsKeyPrefix + ErrAccountNotFound = types.ErrAccountNotFound + ErrClaimExpired = types.ErrClaimExpired + ErrClaimNotFound = types.ErrClaimNotFound + ErrDepositNotFound = types.ErrDepositNotFound + ErrGovScheduleNotFound = types.ErrGovScheduleNotFound + ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance + ErrInvaliWithdrawAmount = types.ErrInvalidWithdrawAmount + ErrInvalidAccountType = types.ErrInvalidAccountType + ErrInvalidDepositDenom = types.ErrInvalidDepositDenom + ErrInvalidClaimType = types.ErrInvalidClaimType + ErrInvalidMultiplier = types.ErrInvalidMultiplier + ErrLPScheduleNotFound = types.ErrLPScheduleNotFound + ErrZeroClaim = types.ErrZeroClaim + GovDenom = types.GovDenom + KeyActive = types.KeyActive + ModuleCdc = types.ModuleCdc + PreviousBlockTimeKey = types.PreviousBlockTimeKey ) type ( - Keeper = keeper.Keeper - AccountKeeper = types.AccountKeeper - Borrow = types.Borrow - MoneyMarket = types.MoneyMarket - MoneyMarkets = types.MoneyMarkets - DelegatorDistributionSchedule = types.DelegatorDistributionSchedule - DelegatorDistributionSchedules = types.DelegatorDistributionSchedules - Deposit = types.Deposit - ClaimType = types.ClaimType - DistributionSchedule = types.DistributionSchedule - DistributionSchedules = types.DistributionSchedules - GenesisDistributionTime = types.GenesisDistributionTime - GenesisDistributionTimes = types.GenesisDistributionTimes - GenesisState = types.GenesisState - MsgClaimReward = types.MsgClaimReward - MsgDeposit = types.MsgDeposit - MsgWithdraw = types.MsgWithdraw - Multiplier = types.Multiplier - MultiplierName = types.MultiplierName - Multipliers = types.Multipliers - Params = types.Params - QueryAccountParams = types.QueryAccountParams - QueryClaimParams = types.QueryClaimParams - StakingKeeper = types.StakingKeeper - SupplyKeeper = types.SupplyKeeper + Keeper = keeper.Keeper + AccountKeeper = types.AccountKeeper + Borrow = types.Borrow + MoneyMarket = types.MoneyMarket + MoneyMarkets = types.MoneyMarkets + Deposit = types.Deposit + ClaimType = types.ClaimType + GenesisState = types.GenesisState + MsgClaimReward = types.MsgClaimReward + MsgDeposit = types.MsgDeposit + MsgWithdraw = types.MsgWithdraw + Multiplier = types.Multiplier + MultiplierName = types.MultiplierName + Multipliers = types.Multipliers + Params = types.Params + QueryAccountParams = types.QueryAccountParams + QueryClaimParams = types.QueryClaimParams + StakingKeeper = types.StakingKeeper + SupplyKeeper = types.SupplyKeeper ) diff --git a/x/hard/client/cli/query.go b/x/hard/client/cli/query.go index da10667a..b2594a8c 100644 --- a/x/hard/client/cli/query.go +++ b/x/hard/client/cli/query.go @@ -19,10 +19,9 @@ import ( // flags for cli queries const ( - flagName = "name" - flagDenom = "denom" - flagOwner = "owner" - flagClaimType = "claim-type" + flagName = "name" + flagDenom = "denom" + flagOwner = "owner" ) // GetQueryCmd returns the cli query commands for the module @@ -40,7 +39,6 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { queryModAccountsCmd(queryRoute, cdc), queryDepositsCmd(queryRoute, cdc), queryTotalDepositedCmd(queryRoute, cdc), - queryClaimsCmd(queryRoute, cdc), queryBorrowsCmd(queryRoute, cdc), queryTotalBorrowedCmd(queryRoute, cdc), )...) @@ -177,76 +175,6 @@ func queryDepositsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { return cmd } -func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "claims", - Short: "query hard module claims with optional filters", - Long: strings.TrimSpace(`query for all hard module claims or a specific claim using flags: - - Example: - $ kvcli q hard claims - $ kvcli q hard claims --owner kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny --claim-type lp --denom bnb - $ kvcli q hard claims --claim-type stake --denom ukava - $ kvcli q hard claims --denom btcb`, - ), - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - var owner sdk.AccAddress - var claimType types.ClaimType - - ownerBech := viper.GetString(flagOwner) - denom := viper.GetString(flagDenom) - claimTypeStr := viper.GetString(flagClaimType) - - if len(ownerBech) != 0 { - claimOwner, err := sdk.AccAddressFromBech32(ownerBech) - if err != nil { - return err - } - owner = claimOwner - } - - if len(claimTypeStr) != 0 { - if err := types.ClaimType(claimTypeStr).IsValid(); err != nil { - return err - } - claimType = types.ClaimType(claimTypeStr) - } - - page := viper.GetInt(flags.FlagPage) - limit := viper.GetInt(flags.FlagLimit) - - params := types.NewQueryClaimParams(page, limit, denom, owner, claimType) - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaims) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - cliCtx = cliCtx.WithHeight(height) - - var claims []types.Claim - if err := cdc.UnmarshalJSON(res, &claims); err != nil { - return fmt.Errorf("failed to unmarshal claims: %w", err) - } - return cliCtx.PrintOutput(claims) - }, - } - - cmd.Flags().Int(flags.FlagPage, 1, "pagination page to query for") - cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit (max 100)") - cmd.Flags().String(flagOwner, "", "(optional) filter for claims by owner address") - cmd.Flags().String(flagDenom, "", "(optional) filter for claims by denom") - cmd.Flags().String(flagClaimType, "", "(optional) filter for claims by type (lp or staking)") - return cmd -} - func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "borrows", diff --git a/x/hard/client/cli/tx.go b/x/hard/client/cli/tx.go index 80812767..3db90bfe 100644 --- a/x/hard/client/cli/tx.go +++ b/x/hard/client/cli/tx.go @@ -32,7 +32,6 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { hardTxCmd.AddCommand(flags.PostCommands( getCmdDeposit(cdc), getCmdWithdraw(cdc), - getCmdClaimReward(cdc), getCmdBorrow(cdc), getCmdLiquidate(cdc), getCmdRepay(cdc), @@ -90,35 +89,6 @@ func getCmdWithdraw(cdc *codec.Codec) *cobra.Command { } } -func getCmdClaimReward(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "claim [receiver-addr] [deposit-denom] [deposit-type] [multiplier]", - Short: "claim HARD tokens to receiver address", - Long: strings.TrimSpace( - `sends accumulated HARD tokens from the hard module account to the receiver address. - Note that receiver address should match the sender address, - unless the sender is a validator-vesting account`), - Args: cobra.ExactArgs(4), - Example: fmt.Sprintf( - `%s tx %s claim kava1hgcfsuwc889wtdmt8pjy7qffua9dd2tralu64j bnb lp large --from `, version.ClientName, types.ModuleName, - ), - 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)) - receiver, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - msg := types.NewMsgClaimReward(cliCtx.GetFromAddress(), receiver, args[1], args[2], args[3]) - if err := msg.ValidateBasic(); err != nil { - return err - } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) - }, - } -} - func getCmdBorrow(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "borrow [1000000000ukava]", diff --git a/x/hard/genesis.go b/x/hard/genesis.go index a3fdf188..a5d4dde4 100644 --- a/x/hard/genesis.go +++ b/x/hard/genesis.go @@ -21,12 +21,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper types.SupplyKeeper, gs k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime) } - for _, pdt := range gs.PreviousDistributionTimes { - if !pdt.PreviousDistributionTime.Equal(DefaultPreviousBlockTime) { - k.SetPreviousDelegationDistribution(ctx, pdt.PreviousDistributionTime, pdt.Denom) - } - } - for _, mm := range gs.Params.MoneyMarkets { k.SetMoneyMarket(ctx, mm.Denom, mm) } @@ -64,12 +58,5 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { if !found { previousBlockTime = DefaultPreviousBlockTime } - previousDistTimes := GenesisDistributionTimes{} - for _, dds := range params.DelegatorDistributionSchedules { - previousDistTime, found := k.GetPreviousDelegatorDistribution(ctx, dds.DistributionSchedule.DepositDenom) - if found { - previousDistTimes = append(previousDistTimes, GenesisDistributionTime{PreviousDistributionTime: previousDistTime, Denom: dds.DistributionSchedule.DepositDenom}) - } - } - return NewGenesisState(params, previousBlockTime, previousDistTimes) + return NewGenesisState(params, previousBlockTime) } diff --git a/x/hard/handler.go b/x/hard/handler.go index 4d4d6c71..a8cfd7c3 100644 --- a/x/hard/handler.go +++ b/x/hard/handler.go @@ -1,8 +1,6 @@ package hard import ( - "strings" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -15,8 +13,6 @@ func NewHandler(k 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) case types.MsgDeposit: return handleMsgDeposit(ctx, k, msg) case types.MsgWithdraw: @@ -33,24 +29,6 @@ func NewHandler(k Keeper) sdk.Handler { } } -func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimReward) (*sdk.Result, error) { - err := k.ClaimReward(ctx, msg.Sender, msg.Receiver, msg.DepositDenom, types.ClaimType(strings.ToLower(msg.ClaimType)), types.MultiplierName(strings.ToLower(msg.MultiplierName))) - 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 -} - func handleMsgDeposit(ctx sdk.Context, k keeper.Keeper, msg types.MsgDeposit) (*sdk.Result, error) { err := k.Deposit(ctx, msg.Depositor, msg.Amount) if err != nil { diff --git a/x/hard/keeper/borrow.go b/x/hard/keeper/borrow.go index 8676f11f..ee200db7 100644 --- a/x/hard/keeper/borrow.go +++ b/x/hard/keeper/borrow.go @@ -28,7 +28,12 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins return err } - // Sync any outstanding interest + // Call incentive hook + existingBorrow, hasExistingBorrow := k.GetBorrow(ctx, borrower) + if hasExistingBorrow { + k.BeforeBorrowModified(ctx, existingBorrow) + } + k.SyncBorrowInterest(ctx, borrower) // Validate borrow amount within user and protocol limits @@ -84,7 +89,6 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins } else { amount = coins } - // Construct the user's new/updated borrow with amount and interest factors borrow := types.NewBorrow(borrower, amount, borrowInterestFactors) @@ -104,6 +108,12 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins // it has already been included in the total borrowed coins by the BeginBlocker. k.IncrementBorrowedCoins(ctx, coins) + if !hasExistingBorrow { + k.AfterBorrowCreated(ctx, borrow) + } else { + k.AfterBorrowModified(ctx, borrow) + } + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeHardBorrow, diff --git a/x/hard/keeper/borrow_test.go b/x/hard/keeper/borrow_test.go index 772d4509..cff74fda 100644 --- a/x/hard/keeper/borrow_test.go +++ b/x/hard/keeper/borrow_test.go @@ -262,19 +262,6 @@ func (suite *KeeperTestSuite) TestBorrow() { // hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "btcb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "busd", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "xyz", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(true, tc.args.usdxBorrowLimit, sdk.MustNewDecFromStr("1")), "usdx:usd", sdk.NewInt(USDX_CF), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("busd", types.NewBorrowLimit(false, sdk.NewDec(100000000*BUSD_CF), sdk.MustNewDecFromStr("1")), "busd:usd", sdk.NewInt(BUSD_CF), sdk.NewInt(BUSD_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), @@ -284,7 +271,7 @@ func (suite *KeeperTestSuite) TestBorrow() { types.NewMoneyMarket("xyz", types.NewBorrowLimit(false, sdk.NewDec(1), tc.args.loanToValueBNB), "xyz:usd", sdk.NewInt(1), sdk.NewInt(1), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/keeper/claim.go b/x/hard/keeper/claim.go deleted file mode 100644 index 61f641d8..00000000 --- a/x/hard/keeper/claim.go +++ /dev/null @@ -1,149 +0,0 @@ -package keeper - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/kava-labs/kava/x/hard/types" - validatorvesting "github.com/kava-labs/kava/x/validator-vesting" -) - -const ( - // BeginningOfMonth hard rewards that are claimed after the 15th at 14:00UTC of the month always vest on the first of the month - BeginningOfMonth = 1 - // MidMonth hard rewards that are claimed before the 15th at 14:00UTC of the month always vest on the 15 of the month - MidMonth = 15 - // PaymentHour hard rewards always vest at 14:00UTC - PaymentHour = 14 -) - -// ClaimReward sends the reward amount to the reward owner and deletes the claim from the store -func (k Keeper) ClaimReward(ctx sdk.Context, claimHolder sdk.AccAddress, receiver sdk.AccAddress, depositDenom string, claimType types.ClaimType, multiplier types.MultiplierName) error { - - claim, found := k.GetClaim(ctx, claimHolder, depositDenom, claimType) - if !found { - return sdkerrors.Wrapf(types.ErrClaimNotFound, "no %s %s claim found for %s", depositDenom, claimType, claimHolder) - } - - err := k.validateSenderReceiver(ctx, claimHolder, receiver) - if err != nil { - return err - } - - switch claimType { - case types.LP: - err = k.claimLPReward(ctx, claim, receiver, multiplier) - case types.Stake: - err = k.claimDelegatorReward(ctx, claim, receiver, multiplier) - default: - return sdkerrors.Wrap(types.ErrInvalidClaimType, string(claimType)) - } - if err != nil { - return err - } - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeClaimHardReward, - sdk.NewAttribute(sdk.AttributeKeyAmount, claim.Amount.String()), - sdk.NewAttribute(types.AttributeKeyClaimHolder, claimHolder.String()), - sdk.NewAttribute(types.AttributeKeyDepositDenom, depositDenom), - sdk.NewAttribute(types.AttributeKeyClaimType, string(claimType)), - sdk.NewAttribute(types.AttributeKeyClaimMultiplier, string(multiplier)), - ), - ) - k.DeleteClaim(ctx, claim) - return nil -} - -// GetPeriodLength returns the length of the period based on the input blocktime and multiplier -// note that pay dates are always the 1st or 15th of the month at 14:00UTC. -func (k Keeper) GetPeriodLength(ctx sdk.Context, multiplier types.Multiplier) (int64, error) { - - if multiplier.MonthsLockup == 0 { - return 0, nil - } - switch multiplier.Name { - case types.Small, types.Medium, types.Large: - currentDay := ctx.BlockTime().Day() - payDay := BeginningOfMonth - monthOffset := int64(1) - if currentDay < MidMonth || (currentDay == MidMonth && ctx.BlockTime().Hour() < PaymentHour) { - payDay = MidMonth - monthOffset = int64(0) - } - periodEndDate := time.Date(ctx.BlockTime().Year(), ctx.BlockTime().Month(), payDay, PaymentHour, 0, 0, 0, time.UTC).AddDate(0, int(multiplier.MonthsLockup+monthOffset), 0) - return periodEndDate.Unix() - ctx.BlockTime().Unix(), nil - } - return 0, types.ErrInvalidMultiplier -} - -func (k Keeper) claimLPReward(ctx sdk.Context, claim types.Claim, receiver sdk.AccAddress, multiplierName types.MultiplierName) error { - lps, found := k.GetLPSchedule(ctx, claim.DepositDenom) - if !found { - return sdkerrors.Wrapf(types.ErrLPScheduleNotFound, claim.DepositDenom) - } - multiplier, found := lps.GetMultiplier(multiplierName) - if !found { - return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) - } - if ctx.BlockTime().After(lps.ClaimEnd) { - return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), lps.ClaimEnd) - } - rewardAmount := sdk.NewDecFromInt(claim.Amount.Amount).Mul(multiplier.Factor).RoundInt() - if rewardAmount.IsZero() { - return types.ErrZeroClaim - } - rewardCoin := sdk.NewCoin(claim.Amount.Denom, rewardAmount) - length, err := k.GetPeriodLength(ctx, multiplier) - if err != nil { - return err - } - - return k.SendTimeLockedCoinsToAccount(ctx, types.LPAccount, receiver, sdk.NewCoins(rewardCoin), length) -} - -func (k Keeper) claimDelegatorReward(ctx sdk.Context, claim types.Claim, receiver sdk.AccAddress, multiplierName types.MultiplierName) error { - dss, found := k.GetDelegatorSchedule(ctx, claim.DepositDenom) - if !found { - return sdkerrors.Wrapf(types.ErrLPScheduleNotFound, claim.DepositDenom) - } - multiplier, found := dss.DistributionSchedule.GetMultiplier(multiplierName) - if !found { - return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName)) - } - if ctx.BlockTime().After(dss.DistributionSchedule.ClaimEnd) { - return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), dss.DistributionSchedule.ClaimEnd) - } - rewardAmount := sdk.NewDecFromInt(claim.Amount.Amount).Mul(multiplier.Factor).RoundInt() - if rewardAmount.IsZero() { - return types.ErrZeroClaim - } - rewardCoin := sdk.NewCoin(claim.Amount.Denom, rewardAmount) - - length, err := k.GetPeriodLength(ctx, multiplier) - if err != nil { - return err - } - - return k.SendTimeLockedCoinsToAccount(ctx, types.DelegatorAccount, receiver, sdk.NewCoins(rewardCoin), length) -} - -func (k Keeper) validateSenderReceiver(ctx sdk.Context, sender, receiver sdk.AccAddress) error { - senderAcc := k.accountKeeper.GetAccount(ctx, sender) - if senderAcc == nil { - return sdkerrors.Wrapf(types.ErrAccountNotFound, sender.String()) - } - switch senderAcc.(type) { - case *validatorvesting.ValidatorVestingAccount: - if sender.Equals(receiver) { - return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", senderAcc) - } - default: - if !sender.Equals(receiver) { - return sdkerrors.Wrapf(types.ErrInvalidReceiver, "%s", sender) - } - } - return nil -} diff --git a/x/hard/keeper/claim_test.go b/x/hard/keeper/claim_test.go deleted file mode 100644 index 7f00ad44..00000000 --- a/x/hard/keeper/claim_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package keeper_test - -import ( - "strings" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/vesting" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - - "github.com/kava-labs/kava/app" - "github.com/kava-labs/kava/x/hard/types" - validatorvesting "github.com/kava-labs/kava/x/validator-vesting" -) - -func (suite *KeeperTestSuite) TestClaim() { - type args struct { - claimOwner sdk.AccAddress - receiver sdk.AccAddress - denom string - claimType types.ClaimType - multiplier types.MultiplierName - blockTime time.Time - createClaim bool - claimAmount sdk.Coin - validatorVesting bool - expectedAccountBalance sdk.Coins - expectedModAccountBalance sdk.Coins - expectedVestingAccount bool - expectedVestingLength int64 - } - type errArgs struct { - expectPass bool - contains string - } - type claimTest struct { - name string - args args - errArgs errArgs - } - testCases := []claimTest{ - { - "valid liquid claim", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(33)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(967))), - expectedVestingAccount: false, - expectedVestingLength: 0, - multiplier: types.Small, - }, - errArgs{ - expectPass: true, - contains: "", - }, - }, - { - "valid liquid delegator claim", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.Stake, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(33)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(967))), - expectedVestingAccount: false, - expectedVestingLength: 0, - multiplier: types.Small, - }, - errArgs{ - expectPass: true, - contains: "", - }, - }, - { - "valid medium vesting claim", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(50)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(950))), - expectedVestingAccount: true, - expectedVestingLength: 16848000, - multiplier: types.Medium, - }, - errArgs{ - expectPass: true, - contains: "", - }, - }, - { - "valid large vesting claim", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))), - expectedVestingAccount: true, - expectedVestingLength: 64281600, - multiplier: types.Large, - }, - errArgs{ - expectPass: true, - contains: "", - }, - }, - { - "valid validator vesting", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test2"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: true, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))), - expectedVestingAccount: true, - expectedVestingLength: 64281600, - multiplier: types.Large, - }, - errArgs{ - expectPass: true, - contains: "", - }, - }, - { - "invalid validator vesting", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: true, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))), - expectedVestingAccount: true, - expectedVestingLength: 64281600, - multiplier: types.Large, - }, - errArgs{ - expectPass: false, - contains: "receiver account type not supported", - }, - }, - { - "claim not found", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: false, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.Coins{}, - expectedModAccountBalance: sdk.Coins{}, - expectedVestingAccount: false, - expectedVestingLength: 0, - multiplier: types.Small, - }, - errArgs{ - expectPass: false, - contains: "claim not found", - }, - }, - { - "claim expired", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2022, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.Coins{}, - expectedModAccountBalance: sdk.Coins{}, - expectedVestingAccount: false, - expectedVestingLength: 0, - multiplier: types.Small, - }, - errArgs{ - expectPass: false, - contains: "claim period expired", - }, - }, - { - "different receiver address", - args{ - claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - receiver: sdk.AccAddress(crypto.AddressHash([]byte("test2"))), - denom: "bnb", - claimType: types.LP, - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - createClaim: true, - claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)), - validatorVesting: false, - expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))), - expectedVestingAccount: true, - expectedVestingLength: 64281600, - multiplier: types.Large, - }, - errArgs{ - expectPass: false, - contains: "receiver account must match sender account", - }, - }, - } - for _, tc := range testCases { - suite.Run(tc.name, func() { - // create new app with one funded account - - // Initialize test app and set context - tApp := app.NewTestApp() - ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime}) - authGS := app.NewAuthGenState( - []sdk.AccAddress{tc.args.claimOwner, tc.args.receiver}, - []sdk.Coins{ - sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))), - }) - loanToValue := sdk.MustNewDecFromStr("0.6") - hardGS := types.NewGenesisState(types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - types.MoneyMarkets{ - types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - }, - 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) - tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) - if tc.args.validatorVesting { - ak := tApp.GetAccountKeeper() - acc := ak.GetAccount(ctx, tc.args.claimOwner) - bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) - bva, err := vesting.NewBaseVestingAccount( - bacc, - sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(20))), time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix()+100) - suite.Require().NoError(err) - vva := validatorvesting.NewValidatorVestingAccountRaw( - bva, - time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix(), - vesting.Periods{ - vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}, - vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}, - vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}, - vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}}, - sdk.ConsAddress(crypto.AddressHash([]byte("test"))), - sdk.AccAddress{}, - 95, - ) - err = vva.Validate() - suite.Require().NoError(err) - ak.SetAccount(ctx, vva) - } - supplyKeeper := tApp.GetSupplyKeeper() - supplyKeeper.MintCoins(ctx, types.LPAccount, sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(1000)))) - supplyKeeper.MintCoins(ctx, types.DelegatorAccount, sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(1000)))) - keeper := tApp.GetHardKeeper() - suite.app = tApp - suite.ctx = ctx - suite.keeper = keeper - - if tc.args.createClaim { - claim := types.NewClaim(tc.args.claimOwner, tc.args.denom, tc.args.claimAmount, tc.args.claimType) - suite.Require().NotPanics(func() { suite.keeper.SetClaim(suite.ctx, claim) }) - } - - err := suite.keeper.ClaimReward(suite.ctx, tc.args.claimOwner, tc.args.receiver, tc.args.denom, tc.args.claimType, tc.args.multiplier) - if tc.errArgs.expectPass { - suite.Require().NoError(err) - acc := suite.getAccount(tc.args.receiver) - suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins()) - mAcc := suite.getModuleAccount(types.LPAccount) - if tc.args.claimType == types.Stake { - mAcc = suite.getModuleAccount(types.DelegatorAccount) - } - suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins()) - vacc, ok := acc.(*vesting.PeriodicVestingAccount) - if tc.args.expectedVestingAccount { - suite.Require().True(ok) - suite.Require().Equal(tc.args.expectedVestingLength, vacc.VestingPeriods[0].Length) - } else { - suite.Require().False(ok) - } - _, f := suite.keeper.GetClaim(ctx, tc.args.claimOwner, tc.args.denom, tc.args.claimType) - suite.Require().False(f) - } else { - suite.Require().Error(err) - suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) - } - }) - } -} - -func (suite *KeeperTestSuite) TestGetPeriodLength() { - type args struct { - blockTime time.Time - multiplier types.Multiplier - expectedLength int64 - } - type errArgs struct { - expectPass bool - contains string - } - type periodTest struct { - name string - args args - errArgs errArgs - } - testCases := []periodTest{ - { - name: "first half of month", - args: args{ - blockTime: time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2021, 5, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "first half of month long lockup", - args: args{ - blockTime: time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2022, 11, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "second half of month", - args: args{ - blockTime: time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2021, 7, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "second half of month long lockup", - args: args{ - blockTime: time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Large, 24, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2023, 1, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "end of feb", - args: args{ - blockTime: time.Date(2021, 2, 28, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2021, 9, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2021, 2, 28, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "leap year", - args: args{ - blockTime: time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2020, 9, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "leap year long lockup", - args: args{ - blockTime: time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Large, 24, sdk.MustNewDecFromStr("1")), - expectedLength: time.Date(2022, 3, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "exactly half of month", - args: args{ - blockTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2021, 7, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - { - name: "just before half of month", - args: args{ - blockTime: time.Date(2020, 12, 15, 13, 59, 59, 0, time.UTC), - multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")), - expectedLength: time.Date(2021, 6, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 15, 13, 59, 59, 0, time.UTC).Unix(), - }, - errArgs: errArgs{ - expectPass: true, - contains: "", - }, - }, - } - for _, tc := range testCases { - suite.Run(tc.name, func() { - ctx := suite.ctx.WithBlockTime(tc.args.blockTime) - length, err := suite.keeper.GetPeriodLength(ctx, tc.args.multiplier) - if tc.errArgs.expectPass { - suite.Require().NoError(err) - suite.Require().Equal(tc.args.expectedLength, length) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/x/hard/keeper/deposit.go b/x/hard/keeper/deposit.go index e43b9c2e..1fbdfa7e 100644 --- a/x/hard/keeper/deposit.go +++ b/x/hard/keeper/deposit.go @@ -29,6 +29,12 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi return err } + // Call incentive hook + existingDeposit, hasExistingDeposit := k.GetDeposit(ctx, depositor) + if hasExistingDeposit { + k.BeforeDepositModified(ctx, existingDeposit) + } + // Sync any outstanding interest k.SyncBorrowInterest(ctx, depositor) k.SyncSupplyInterest(ctx, depositor) @@ -98,10 +104,12 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi } k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv) - - // Update total supplied amount by newly supplied coins. Don't add user's pending interest as - // it has already been included in the total supplied coins by the BeginBlocker. k.IncrementSuppliedCoins(ctx, coins) + if !foundDeposit { // User's first deposit + k.AfterDepositCreated(ctx, deposit) + } else { + k.AfterDepositModified(ctx, deposit) + } ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -116,16 +124,10 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi // ValidateDeposit validates a deposit func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error { - params := k.GetParams(ctx) for _, depCoin := range coins { - found := false - for _, lps := range params.LiquidityProviderSchedules { - if lps.DepositDenom == depCoin.Denom { - found = true - } - } - if !found { - return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, "liquidity provider denom %s not found", depCoin.Denom) + _, foundMm := k.GetMoneyMarket(ctx, depCoin.Denom) + if !foundMm { + return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, "money market denom %s not found", depCoin.Denom) } } diff --git a/x/hard/keeper/deposit_test.go b/x/hard/keeper/deposit_test.go index 39fdea5d..0a361c91 100644 --- a/x/hard/keeper/deposit_test.go +++ b/x/hard/keeper/deposit_test.go @@ -68,7 +68,7 @@ func (suite *KeeperTestSuite) TestDeposit() { "invalid deposit denom", args{ depositor: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - amount: sdk.NewCoins(sdk.NewCoin("btcb", sdk.NewInt(100))), + amount: sdk.NewCoins(sdk.NewCoin("fake", sdk.NewInt(100))), numberDeposits: 1, expectedAccountBalance: sdk.Coins{}, expectedModAccountBalance: sdk.Coins{}, @@ -106,14 +106,6 @@ func (suite *KeeperTestSuite) TestDeposit() { loanToValue, _ := sdk.NewDecFromStr("0.6") hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), @@ -121,7 +113,7 @@ func (suite *KeeperTestSuite) TestDeposit() { types.NewMoneyMarket("btcb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "btcb:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/keeper/hooks.go b/x/hard/keeper/hooks.go new file mode 100644 index 00000000..829ee2e6 --- /dev/null +++ b/x/hard/keeper/hooks.go @@ -0,0 +1,52 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/hard/types" +) + +// Implements StakingHooks interface +var _ types.HARDHooks = Keeper{} + +// AfterDepositCreated - call hook if registered +func (k Keeper) AfterDepositCreated(ctx sdk.Context, deposit types.Deposit) { + if k.hooks != nil { + k.hooks.AfterDepositCreated(ctx, deposit) + } +} + +// BeforeDepositModified - call hook if registered +func (k Keeper) BeforeDepositModified(ctx sdk.Context, deposit types.Deposit) { + if k.hooks != nil { + k.hooks.BeforeDepositModified(ctx, deposit) + } +} + +// AfterDepositModified - call hook if registered +func (k Keeper) AfterDepositModified(ctx sdk.Context, deposit types.Deposit) { + if k.hooks != nil { + k.hooks.AfterDepositModified(ctx, deposit) + } +} + +// AfterBorrowCreated - call hook if registered +func (k Keeper) AfterBorrowCreated(ctx sdk.Context, borrow types.Borrow) { + if k.hooks != nil { + k.hooks.AfterBorrowCreated(ctx, borrow) + } +} + +// BeforeBorrowModified - call hook if registered +func (k Keeper) BeforeBorrowModified(ctx sdk.Context, borrow types.Borrow) { + if k.hooks != nil { + k.hooks.BeforeBorrowModified(ctx, borrow) + } +} + +// AfterBorrowModified - call hook if registered +func (k Keeper) AfterBorrowModified(ctx sdk.Context, borrow types.Borrow) { + if k.hooks != nil { + k.hooks.AfterBorrowModified(ctx, borrow) + } +} diff --git a/x/hard/keeper/interest.go b/x/hard/keeper/interest.go index 703c774a..b527754f 100644 --- a/x/hard/keeper/interest.go +++ b/x/hard/keeper/interest.go @@ -135,7 +135,6 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error { k.IncrementBorrowedCoins(ctx, totalBorrowInterestAccumulated) k.IncrementSuppliedCoins(ctx, sdk.NewCoins(sdk.NewCoin(denom, supplyInterestNew))) k.SetTotalReserves(ctx, denom, reservesPrior.Add(sdk.NewCoin(mm.Denom, reservesNew))) - k.SetSupplyInterestFactor(ctx, denom, supplyInterestFactorNew) k.SetPreviousAccrualTime(ctx, denom, ctx.BlockTime()) return nil diff --git a/x/hard/keeper/interest_test.go b/x/hard/keeper/interest_test.go index 05e65523..1760c549 100644 --- a/x/hard/keeper/interest_test.go +++ b/x/hard/keeper/interest_test.go @@ -712,14 +712,6 @@ func (suite *KeeperTestSuite) TestBorrowInterest() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit @@ -731,7 +723,7 @@ func (suite *KeeperTestSuite) TestBorrowInterest() { sdk.ZeroDec()), // Keeper Reward Percentage }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ @@ -1127,15 +1119,6 @@ func (suite *KeeperTestSuite) TestSupplyInterest() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit @@ -1155,7 +1138,7 @@ func (suite *KeeperTestSuite) TestSupplyInterest() { sdk.ZeroDec()), // Keeper Reward Percentage }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/keeper/keeper.go b/x/hard/keeper/keeper.go index 4caf3dea..cbf83fca 100644 --- a/x/hard/keeper/keeper.go +++ b/x/hard/keeper/keeper.go @@ -21,6 +21,7 @@ type Keeper struct { stakingKeeper types.StakingKeeper pricefeedKeeper types.PricefeedKeeper auctionKeeper types.AuctionKeeper + hooks types.HARDHooks } // NewKeeper creates a new keeper @@ -40,9 +41,19 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, stakingKeeper: stk, pricefeedKeeper: pfk, auctionKeeper: auk, + hooks: nil, } } +// SetHooks sets the cdp keeper hooks +func (k *Keeper) SetHooks(hooks types.HARDHooks) *Keeper { + if k.hooks != nil { + panic("cannot set validator hooks twice") + } + k.hooks = hooks + return k +} + // 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) @@ -60,23 +71,6 @@ func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) { store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime)) } -// GetPreviousDelegatorDistribution get the time of the previous delegator distribution -func (k Keeper) GetPreviousDelegatorDistribution(ctx sdk.Context, denom string) (distTime time.Time, found bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDelegationDistributionKey) - bz := store.Get([]byte(denom)) - if bz == nil { - return time.Time{}, false - } - k.cdc.MustUnmarshalBinaryBare(bz, &distTime) - return distTime, true -} - -// SetPreviousDelegationDistribution set the time of the previous delegator distribution -func (k Keeper) SetPreviousDelegationDistribution(ctx sdk.Context, distTime time.Time, denom string) { - store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDelegationDistributionKey) - store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(distTime)) -} - // GetDeposit returns a deposit from the store for a particular depositor address, deposit denom func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) { store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix) @@ -116,59 +110,6 @@ func (k Keeper) IterateDeposits(ctx sdk.Context, cb func(deposit types.Deposit) } } -// GetClaim returns a claim from the store for a particular claim owner, deposit denom, and claim type -func (k Keeper) GetClaim(ctx sdk.Context, owner sdk.AccAddress, depositDenom string, claimType types.ClaimType) (types.Claim, bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix) - bz := store.Get(types.ClaimKey(claimType, depositDenom, owner)) - if bz == nil { - return types.Claim{}, false - } - var claim types.Claim - k.cdc.MustUnmarshalBinaryBare(bz, &claim) - return claim, true -} - -// SetClaim stores the input claim in the store, prefixed by the deposit type, deposit denom, and owner address, in that order -func (k Keeper) SetClaim(ctx sdk.Context, claim types.Claim) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix) - bz := k.cdc.MustMarshalBinaryBare(claim) - store.Set(types.ClaimKey(claim.Type, claim.DepositDenom, claim.Owner), bz) -} - -// DeleteClaim deletes a claim from the store -func (k Keeper) DeleteClaim(ctx sdk.Context, claim types.Claim) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix) - store.Delete(types.ClaimKey(claim.Type, claim.DepositDenom, claim.Owner)) -} - -// IterateClaims iterates over all claim objects in the store and performs a callback function -func (k Keeper) IterateClaims(ctx sdk.Context, cb func(claim types.Claim) (stop bool)) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix) - iterator := sdk.KVStorePrefixIterator(store, []byte{}) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var claim types.Claim - k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &claim) - if cb(claim) { - break - } - } -} - -// IterateClaimsByTypeAndDenom iterates over all claim objects in the store with the matching claim type and deposit denom and performs a callback function -func (k Keeper) IterateClaimsByTypeAndDenom(ctx sdk.Context, claimType types.ClaimType, depositDenom string, cb func(claim types.Claim) (stop bool)) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix) - iterator := sdk.KVStorePrefixIterator(store, types.ClaimTypeIteratorKey(claimType, depositDenom)) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var claim types.Claim - k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &claim) - if cb(claim) { - break - } - } -} - // GetDepositsByUser gets all deposits for an individual user func (k Keeper) GetDepositsByUser(ctx sdk.Context, user sdk.AccAddress) []types.Deposit { var deposits []types.Deposit diff --git a/x/hard/keeper/keeper_test.go b/x/hard/keeper/keeper_test.go index 55a5c0b9..02167932 100644 --- a/x/hard/keeper/keeper_test.go +++ b/x/hard/keeper/keeper_test.go @@ -60,21 +60,6 @@ func (suite *KeeperTestSuite) TestGetSetPreviousBlockTime() { suite.Equal(now, pbt) } -func (suite *KeeperTestSuite) TestGetSetPreviousDelegatorDistribution() { - now := tmtime.Now() - - _, f := suite.keeper.GetPreviousDelegatorDistribution(suite.ctx, suite.keeper.BondDenom(suite.ctx)) - suite.Require().False(f) - - suite.NotPanics(func() { - suite.keeper.SetPreviousDelegationDistribution(suite.ctx, now, suite.keeper.BondDenom(suite.ctx)) - }) - - pdt, f := suite.keeper.GetPreviousDelegatorDistribution(suite.ctx, suite.keeper.BondDenom(suite.ctx)) - suite.True(f) - suite.Equal(now, pdt) -} - func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() { dep := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))), types.SupplyInterestFactors{types.NewSupplyInterestFactor("", sdk.MustNewDecFromStr("0"))}) @@ -108,21 +93,6 @@ func (suite *KeeperTestSuite) TestIterateDeposits() { suite.Require().Equal(5, len(deposits)) } -func (suite *KeeperTestSuite) TestGetSetDeleteClaim() { - claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "lp") - _, f := suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp") - suite.Require().False(f) - - suite.Require().NotPanics(func() { suite.keeper.SetClaim(suite.ctx, claim) }) - testClaim, f := suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp") - suite.Require().True(f) - suite.Require().Equal(claim, testClaim) - - suite.Require().NotPanics(func() { suite.keeper.DeleteClaim(suite.ctx, claim) }) - _, f = suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp") - suite.Require().False(f) -} - func (suite *KeeperTestSuite) TestGetSetDeleteInterestRateModel() { denom := "test" model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")) diff --git a/x/hard/keeper/liquidation.go b/x/hard/keeper/liquidation.go index d4bed3b4..7d80cfa5 100644 --- a/x/hard/keeper/liquidation.go +++ b/x/hard/keeper/liquidation.go @@ -39,9 +39,6 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, return err } - k.SyncBorrowInterest(ctx, borrower) - k.SyncSupplyInterest(ctx, borrower) - deposit, found := k.GetDeposit(ctx, borrower) if !found { return types.ErrDepositNotFound @@ -52,6 +49,23 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, return types.ErrBorrowNotFound } + // Call incentive hooks + k.BeforeDepositModified(ctx, deposit) + k.BeforeBorrowModified(ctx, borrow) + + k.SyncBorrowInterest(ctx, borrower) + k.SyncSupplyInterest(ctx, borrower) + + deposit, found = k.GetDeposit(ctx, borrower) + if !found { + return types.ErrDepositNotFound + } + + borrow, found = k.GetBorrow(ctx, borrower) + if !found { + return types.ErrBorrowNotFound + } + isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow) if err != nil { return err diff --git a/x/hard/keeper/liquidation_test.go b/x/hard/keeper/liquidation_test.go index 02bc76ad..af082793 100644 --- a/x/hard/keeper/liquidation_test.go +++ b/x/hard/keeper/liquidation_test.go @@ -123,20 +123,6 @@ func (suite *KeeperTestSuite) TestIndexLiquidation() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "usdc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "usdt", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "dai", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "btc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit @@ -196,7 +182,7 @@ func (suite *KeeperTestSuite) TestIndexLiquidation() { sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, tc.args.ltvIndexCount, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ @@ -549,15 +535,6 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit @@ -577,7 +554,7 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() { sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, tc.args.ltvIndexCount, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ @@ -1181,20 +1158,6 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "usdc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "usdt", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "dai", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "btc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit @@ -1254,7 +1217,7 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() { tc.args.keeperRewardPercent), // Keeper Reward Percent }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/keeper/params.go b/x/hard/keeper/params.go index 6ec62eb3..e57643e8 100644 --- a/x/hard/keeper/params.go +++ b/x/hard/keeper/params.go @@ -18,28 +18,6 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSubspace.SetParamSet(ctx, ¶ms) } -// GetLPSchedule gets the LP's schedule -func (k Keeper) GetLPSchedule(ctx sdk.Context, denom string) (types.DistributionSchedule, bool) { - params := k.GetParams(ctx) - for _, lps := range params.LiquidityProviderSchedules { - if lps.DepositDenom == denom { - return lps, true - } - } - return types.DistributionSchedule{}, false -} - -// GetDelegatorSchedule gets the Delgator's schedule -func (k Keeper) GetDelegatorSchedule(ctx sdk.Context, denom string) (types.DelegatorDistributionSchedule, bool) { - params := k.GetParams(ctx) - for _, dds := range params.DelegatorDistributionSchedules { - if dds.DistributionSchedule.DepositDenom == denom { - return dds, true - } - } - return types.DelegatorDistributionSchedule{}, false -} - // GetMoneyMarketParam returns the corresponding Money Market param for a specific denom func (k Keeper) GetMoneyMarketParam(ctx sdk.Context, denom string) (types.MoneyMarket, bool) { params := k.GetParams(ctx) diff --git a/x/hard/keeper/querier.go b/x/hard/keeper/querier.go index 9d1b4fe9..5decb61c 100644 --- a/x/hard/keeper/querier.go +++ b/x/hard/keeper/querier.go @@ -24,8 +24,6 @@ func NewQuerier(k Keeper) sdk.Querier { return queryGetDeposits(ctx, req, k) case types.QueryGetTotalDeposited: return queryGetTotalDeposited(ctx, req, k) - case types.QueryGetClaims: - return queryGetClaims(ctx, req, k) case types.QueryGetBorrows: return queryGetBorrows(ctx, req, k) case types.QueryGetTotalBorrowed: @@ -149,111 +147,6 @@ func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, return bz, nil } -func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - - var params types.QueryClaimParams - err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - depositDenom := len(params.Denom) > 0 - owner := len(params.Owner) > 0 - claimType := len(params.ClaimType) > 0 - - var claims []types.Claim - switch { - case depositDenom && owner && claimType: - claim, found := k.GetClaim(ctx, params.Owner, params.Denom, params.ClaimType) - if found { - claims = append(claims, claim) - } - case depositDenom && owner: - for _, dt := range types.ClaimTypesClaimQuery { - claim, found := k.GetClaim(ctx, params.Owner, params.Denom, dt) - if found { - claims = append(claims, claim) - } - } - case depositDenom && claimType: - k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, params.Denom, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - case owner && claimType: - hardParams := k.GetParams(ctx) - switch { - case params.ClaimType == types.LP: - for _, lps := range hardParams.LiquidityProviderSchedules { - claim, found := k.GetClaim(ctx, params.Owner, lps.DepositDenom, params.ClaimType) - if found { - claims = append(claims, claim) - } - } - case params.ClaimType == types.Stake: - for _, dss := range hardParams.DelegatorDistributionSchedules { - claim, found := k.GetClaim(ctx, params.Owner, dss.DistributionSchedule.DepositDenom, params.ClaimType) - if found { - claims = append(claims, claim) - } - } - } - case depositDenom: - for _, dt := range types.ClaimTypesClaimQuery { - k.IterateClaimsByTypeAndDenom(ctx, dt, params.Denom, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - } - case owner: - hardParams := k.GetParams(ctx) - for _, lps := range hardParams.LiquidityProviderSchedules { - claim, found := k.GetClaim(ctx, params.Owner, lps.DepositDenom, types.LP) - if found { - claims = append(claims, claim) - } - } - for _, dds := range hardParams.DelegatorDistributionSchedules { - claim, found := k.GetClaim(ctx, params.Owner, dds.DistributionSchedule.DepositDenom, types.Stake) - if found { - claims = append(claims, claim) - } - } - case claimType: - hardParams := k.GetParams(ctx) - for _, lps := range hardParams.LiquidityProviderSchedules { - k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, lps.DepositDenom, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - } - for _, dds := range hardParams.DelegatorDistributionSchedules { - k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, dds.DistributionSchedule.DepositDenom, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - } - default: - k.IterateClaims(ctx, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - } - - start, end := client.Paginate(len(claims), params.Page, params.Limit, 100) - if start < 0 || end < 0 { - claims = []types.Claim{} - } else { - claims = claims[start:end] - } - - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, claims) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - - return bz, nil -} - func queryGetBorrows(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { var params types.QueryBorrowsParams diff --git a/x/hard/keeper/repay.go b/x/hard/keeper/repay.go index ad27498d..8a36d4ad 100644 --- a/x/hard/keeper/repay.go +++ b/x/hard/keeper/repay.go @@ -15,6 +15,14 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e return err } + // Check borrow exists here to avoid duplicating store read in ValidateRepay + borrow, found := k.GetBorrow(ctx, sender) + if !found { + return types.ErrBorrowNotFound + } + // Call incentive hook + k.BeforeBorrowModified(ctx, borrow) + // Sync borrow interest so loan is up-to-date k.SyncBorrowInterest(ctx, sender) @@ -24,12 +32,6 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e return err } - // Check borrow exists here to avoid duplicating store read in ValidateRepay - borrow, found := k.GetBorrow(ctx, sender) - if !found { - return types.ErrBorrowNotFound - } - payment, err := k.CalculatePaymentAmount(borrow.Amount, coins) if err != nil { return err @@ -59,6 +61,11 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e // Update total borrowed amount k.DecrementBorrowedCoins(ctx, payment) + // Call incentive hook + if !borrow.Amount.Empty() { + k.AfterBorrowModified(ctx, borrow) + } + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeHardRepay, diff --git a/x/hard/keeper/repay_test.go b/x/hard/keeper/repay_test.go index 33d20194..433d776a 100644 --- a/x/hard/keeper/repay_test.go +++ b/x/hard/keeper/repay_test.go @@ -138,15 +138,6 @@ func (suite *KeeperTestSuite) TestRepay() { // Hard module genesis state hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(100000000*USDX_CF), sdk.MustNewDecFromStr("1")), // Borrow Limit @@ -166,7 +157,7 @@ func (suite *KeeperTestSuite) TestRepay() { sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/keeper/rewards.go b/x/hard/keeper/rewards.go deleted file mode 100644 index 3aa0a73e..00000000 --- a/x/hard/keeper/rewards.go +++ /dev/null @@ -1,175 +0,0 @@ -package keeper - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/kava-labs/kava/x/hard/types" -) - -// ApplyDepositRewards iterates over lp and gov deposits and updates the amount of rewards for each depositor -func (k Keeper) ApplyDepositRewards(ctx sdk.Context) { - previousBlockTime, found := k.GetPreviousBlockTime(ctx) - if !found { - previousBlockTime = ctx.BlockTime() - k.SetPreviousBlockTime(ctx, previousBlockTime) - return - } - params := k.GetParams(ctx) - if !params.Active { - return - } - timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix()) - - for _, lps := range params.LiquidityProviderSchedules { - if !lps.Active { - continue - } - if lps.End.Before(ctx.BlockTime()) { - continue - } - if lps.Start.After(ctx.BlockTime()) { - continue - } - totalDeposited := k.GetTotalDeposited(ctx, lps.DepositDenom) - if totalDeposited.IsZero() { - continue - } - rewardsToDistribute := lps.RewardsPerSecond.Amount.Mul(timeElapsed) - if rewardsToDistribute.IsZero() { - continue - } - rewardsDistributed := sdk.ZeroInt() - k.IterateDeposits(ctx, func(dep types.Deposit) (stop bool) { - rewardsShare := sdk.NewDecFromInt(dep.Amount.AmountOf(lps.DepositDenom)).Quo(sdk.NewDecFromInt(totalDeposited)) - if rewardsShare.IsZero() { - return false - } - rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt() - if rewardsEarned.IsZero() { - return false - } - k.AddToClaim(ctx, dep.Depositor, lps.DepositDenom, types.LP, sdk.NewCoin(lps.RewardsPerSecond.Denom, rewardsEarned)) - rewardsDistributed = rewardsDistributed.Add(rewardsEarned) - return false - }) - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeHardLPDistribution, - sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())), - sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()), - sdk.NewAttribute(types.AttributeKeyDepositDenom, lps.DepositDenom), - ), - ) - } - k.SetPreviousBlockTime(ctx, ctx.BlockTime()) -} - -// ShouldDistributeValidatorRewards returns true if enough time has elapsed such that rewards should be distributed to delegators -func (k Keeper) ShouldDistributeValidatorRewards(ctx sdk.Context, denom string) bool { - previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, denom) - if !found { - k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), denom) - return false - } - params := k.GetParams(ctx) - if !params.Active { - return false - } - for _, dds := range params.DelegatorDistributionSchedules { - if denom != dds.DistributionSchedule.DepositDenom { - continue - } - if dds.DistributionSchedule.End.Before(ctx.BlockTime()) { - continue - } - timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix()) - if timeElapsed.GTE(sdk.NewInt(int64(dds.DistributionFrequency.Seconds()))) { - return true - } - } - return false -} - -// ApplyDelegationRewards iterates over each delegation object in the staking store and applies rewards according to the input delegation distribution schedule -func (k Keeper) ApplyDelegationRewards(ctx sdk.Context, denom string) { - dds, found := k.GetDelegatorSchedule(ctx, denom) - if !found { - return - } - if !dds.DistributionSchedule.Active { - return - } - if dds.DistributionSchedule.Start.After(ctx.BlockTime()) { - return - } - bondMacc := k.stakingKeeper.GetBondedPool(ctx) - bondedCoinAmount := bondMacc.GetCoins().AmountOf(dds.DistributionSchedule.DepositDenom) - if bondedCoinAmount.IsZero() { - return - } - previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, dds.DistributionSchedule.DepositDenom) - if !found { - return - } - timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix()) - rewardsToDistribute := dds.DistributionSchedule.RewardsPerSecond.Amount.Mul(timeElapsed) - - // create a map that has each validator address (sdk.ValAddress) as a key and the coversion factor for going from delegator shares to tokens for delegations to that validator. - // If a validator has never been slashed, the conversion factor will be 1.0, if they have been, it will be < 1.0 - sharesToTokens := make(map[string]sdk.Dec) - k.stakingKeeper.IterateValidators(ctx, func(index int64, validator stakingexported.ValidatorI) (stop bool) { - if validator.GetTokens().IsZero() { - return false - } - // don't include a validator if it's unbonded or unbonding- ie delegators don't accumulate rewards when delegated to an unbonded/slashed validator - if validator.GetStatus() != sdk.Bonded { - return false - } - sharesToTokens[validator.GetOperator().String()] = sdk.NewDecFromInt(validator.GetTokens()).Quo(validator.GetDelegatorShares()) - return false - }) - - rewardsDistributed := sdk.ZeroInt() - - k.stakingKeeper.IterateAllDelegations(ctx, func(delegation stakingtypes.Delegation) (stop bool) { - conversionFactor, ok := sharesToTokens[delegation.ValidatorAddress.String()] - if ok { - delegationTokens := conversionFactor.Mul(delegation.Shares) - delegationShare := delegationTokens.Quo(sdk.NewDecFromInt(bondedCoinAmount)) - rewardsEarned := delegationShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt() - if rewardsEarned.IsZero() { - return false - } - k.AddToClaim( - ctx, delegation.DelegatorAddress, dds.DistributionSchedule.DepositDenom, - types.Stake, sdk.NewCoin(dds.DistributionSchedule.RewardsPerSecond.Denom, rewardsEarned)) - rewardsDistributed = rewardsDistributed.Add(rewardsEarned) - } - return false - }) - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeHardDelegatorDistribution, - sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())), - sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()), - sdk.NewAttribute(types.AttributeKeyDepositDenom, denom), - ), - ) - -} - -// AddToClaim adds the input amount to an existing claim or creates a new one -func (k Keeper) AddToClaim(ctx sdk.Context, owner sdk.AccAddress, depositDenom string, claimType types.ClaimType, amountToAdd sdk.Coin) { - claim, found := k.GetClaim(ctx, owner, depositDenom, claimType) - if !found { - claim = types.NewClaim(owner, depositDenom, amountToAdd, claimType) - } else { - claim.Amount = claim.Amount.Add(amountToAdd) - } - k.SetClaim(ctx, claim) -} diff --git a/x/hard/keeper/rewards_test.go b/x/hard/keeper/rewards_test.go deleted file mode 100644 index d9624a8d..00000000 --- a/x/hard/keeper/rewards_test.go +++ /dev/null @@ -1,476 +0,0 @@ -package keeper_test - -import ( - "fmt" - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/stretchr/testify/suite" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - - "github.com/kava-labs/kava/app" - "github.com/kava-labs/kava/x/hard/keeper" - "github.com/kava-labs/kava/x/hard/types" -) - -func (suite *KeeperTestSuite) TestApplyDepositRewards() { - type args struct { - depositor sdk.AccAddress - denom string - depositAmount sdk.Coins - totalDeposits sdk.Coin - rewardRate sdk.Coin - claimType types.ClaimType - previousBlockTime time.Time - blockTime time.Time - expectedClaimBalance sdk.Coin - } - type errArgs struct { - expectPanic bool - contains string - } - type testCase struct { - name string - args args - errArgs errArgs - } - testCases := []testCase{ - { - name: "distribute rewards", - args: args{ - depositor: sdk.AccAddress(crypto.AddressHash([]byte("test"))), - denom: "bnb", - rewardRate: c("hard", 500), - depositAmount: cs(c("bnb", 100)), - totalDeposits: c("bnb", 1000), - claimType: types.LP, - previousBlockTime: time.Date(2020, 11, 1, 13, 59, 50, 0, time.UTC), - blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), - expectedClaimBalance: c("hard", 500), - }, - errArgs: errArgs{ - expectPanic: false, - contains: "", - }, - }, - } - for _, tc := range testCases { - suite.Run(tc.name, func() { - // Initialize test app and set context - tApp := app.NewTestApp() - ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime}) - loanToValue, _ := sdk.NewDecFromStr("0.6") - hardGS := types.NewGenesisState(types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), tc.args.rewardRate, time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), tc.args.rewardRate, time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - types.MoneyMarkets{ - types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - }, - 0, // LTV counter - ), tc.args.previousBlockTime, types.DefaultDistributionTimes) - tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) - supplyKeeper := tApp.GetSupplyKeeper() - supplyKeeper.MintCoins(ctx, types.ModuleAccountName, cs(tc.args.totalDeposits)) - keeper := tApp.GetHardKeeper() - deposit := types.NewDeposit(tc.args.depositor, tc.args.depositAmount, types.SupplyInterestFactors{}) - keeper.SetDeposit(ctx, deposit) - suite.app = tApp - suite.ctx = ctx - suite.keeper = keeper - - if tc.errArgs.expectPanic { - suite.Require().Panics(func() { suite.keeper.ApplyDepositRewards(suite.ctx) }) - } else { - suite.Require().NotPanics(func() { suite.keeper.ApplyDepositRewards(suite.ctx) }) - claim, f := suite.keeper.GetClaim(suite.ctx, tc.args.depositor, tc.args.denom, tc.args.claimType) - suite.Require().True(f) - suite.Require().Equal(tc.args.expectedClaimBalance, claim.Amount) - } - }) - } -} - -func TestApplyDelegatorRewardsTestSuite(t *testing.T) { - suite.Run(t, new(DelegatorRewardsTestSuite)) -} - -type DelegatorRewardsTestSuite struct { - suite.Suite - - validatorAddrs []sdk.ValAddress - delegatorAddrs []sdk.AccAddress - - keeper keeper.Keeper - stakingKeeper staking.Keeper - app app.TestApp - rewardRate int64 -} - -// The default state used by each test -func (suite *DelegatorRewardsTestSuite) SetupTest() { - config := sdk.GetConfig() - app.SetBech32AddressPrefixes(config) - - _, allAddrs := app.GeneratePrivKeyAddressPairs(10) - suite.delegatorAddrs = allAddrs[:5] - for _, a := range allAddrs[5:] { - suite.validatorAddrs = append(suite.validatorAddrs, sdk.ValAddress(a)) - } - - suite.app = app.NewTestApp() - - suite.rewardRate = 500 - - suite.app.InitializeFromGenesisStates( - equalCoinsAuthGenState(allAddrs, cs(c("ukava", 5_000_000))), - stakingGenesisState(), - hardGenesisState(c("hard", suite.rewardRate)), - ) - - suite.keeper = suite.app.GetHardKeeper() - suite.stakingKeeper = suite.app.GetStakingKeeper() -} - -func (suite *DelegatorRewardsTestSuite) TestSlash() { - - blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC) - ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime}) - const rewardDuration = 5 - suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava") - - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 5_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - suite.Require().NoError( - suite.slashValidator(ctx, suite.validatorAddrs[0], "0.05"), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - // Run function under test - suite.keeper.ApplyDelegationRewards(ctx, "ukava") - - // Check claim amounts - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration)), - ) -} - -func (suite *DelegatorRewardsTestSuite) TestUndelegation() { - - blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC) - ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime}) - const rewardDuration = 5 - suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava") - - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - suite.Require().NoError( - suite.deliverMsgUndelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - // Run function under test - suite.keeper.ApplyDelegationRewards(ctx, "ukava") - - // Check claim amounts - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration)), - ) - suite.Require().False( - suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]), - ) -} - -func (suite *DelegatorRewardsTestSuite) TestUnevenNumberDelegations() { - - // Setup a context - blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC) - ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime}) - const rewardDuration = 5 - suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava") - - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - // Run function under test - suite.keeper.ApplyDelegationRewards(ctx, "ukava") - - // Check claim amounts - expectedReward := suite.rewardRate * rewardDuration / 3 // floor division - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", expectedReward)), - ) - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[0], c("hard", expectedReward)), - ) - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", expectedReward)), - ) -} - -func (suite *DelegatorRewardsTestSuite) TestSlashWithUndelegated() { - - // Setup a context - blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC) - ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime}) - const rewardDuration = 5 - suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava") - - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - suite.Require().NoError( - suite.deliverMsgUndelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - suite.Require().NoError( - suite.slashValidator(ctx, suite.validatorAddrs[0], "0.05"), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - // Run function under test - suite.keeper.ApplyDelegationRewards(ctx, "ukava") - - // Check claim amounts - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration/2)), - ) - suite.Require().False( - suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]), - ) - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", suite.rewardRate*rewardDuration/2)), - ) -} -func (suite *DelegatorRewardsTestSuite) TestUnbondingValidator() { - - // Setup a context - blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC) - ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime}) - const rewardDuration = 5 - suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava") - - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[1], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)), - ) - suite.Require().NoError( - suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[1], c("ukava", 1_000_000)), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - suite.Require().NoError( - // jail the validator to put it into an unbonding state - suite.jailValidator(ctx, suite.validatorAddrs[0]), - ) - staking.EndBlocker(ctx, suite.stakingKeeper) - - // Run function under test - suite.keeper.ApplyDelegationRewards(ctx, "ukava") - - // Check claim amounts - suite.Require().False( - // validator 0 will be unbonding and should not receive rewards - suite.kavaClaimExists(ctx, sdk.AccAddress(suite.validatorAddrs[0])), - ) - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[1]), c("hard", suite.rewardRate*rewardDuration/2)), - ) - suite.Require().False( - // delegations to unbonding validators and should not receive rewards - suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]), - ) - suite.Require().NoError( - suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", suite.rewardRate*rewardDuration/2)), - ) -} - -func (suite *DelegatorRewardsTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error { - msg := staking.NewMsgCreateValidator( - address, - ed25519.GenPrivKey().PubKey(), - selfDelegation, - staking.Description{}, - staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - sdk.NewInt(1_000_000), - ) - handleStakingMsg := staking.NewHandler(suite.stakingKeeper) - _, err := handleStakingMsg(ctx, msg) - return err -} -func (suite *DelegatorRewardsTestSuite) deliverMsgDelegate(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error { - msg := staking.NewMsgDelegate( - delegator, - validator, - amount, - ) - handleStakingMsg := staking.NewHandler(suite.stakingKeeper) - _, err := handleStakingMsg(ctx, msg) - return err -} -func (suite *DelegatorRewardsTestSuite) deliverMsgUndelegate(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error { - msg := staking.NewMsgUndelegate( - delegator, - validator, - amount, - ) - handleStakingMsg := staking.NewHandler(suite.stakingKeeper) - _, err := handleStakingMsg(ctx, msg) - return err -} - -func (suite *DelegatorRewardsTestSuite) slashValidator(ctx sdk.Context, validator sdk.ValAddress, slashPercent string) error { - // Assume slashable offence occurred at block 1. Note this might cause problems if tests are running at a block height higher than the unbonding period (default 3 weeks) - const infractionHeight int64 = 1 - - val, found := suite.stakingKeeper.GetValidator(ctx, validator) - if !found { - return fmt.Errorf("can't find validator in state") - } - suite.stakingKeeper.Slash( - ctx, - sdk.GetConsAddress(val.ConsPubKey), - infractionHeight, - val.GetConsensusPower(), - sdk.MustNewDecFromStr(slashPercent), - ) - return nil -} -func (suite *DelegatorRewardsTestSuite) jailValidator(ctx sdk.Context, validator sdk.ValAddress) error { - val, found := suite.stakingKeeper.GetValidator(ctx, validator) - if !found { - return fmt.Errorf("can't find validator in state") - } - suite.stakingKeeper.Jail(ctx, sdk.GetConsAddress(val.ConsPubKey)) - return nil -} - -// verifyKavaClaimAmount looks up a ukava claim and checks the claim amount is equal to an expected value -func (suite *DelegatorRewardsTestSuite) verifyKavaClaimAmount(ctx sdk.Context, owner sdk.AccAddress, expectedAmount sdk.Coin) error { - claim, found := suite.keeper.GetClaim(ctx, owner, "ukava", types.Stake) - if !found { - return fmt.Errorf("could not find claim") - } - if !expectedAmount.IsEqual(claim.Amount) { - return fmt.Errorf("expected claim amount (%s) != actual claim amount (%s)", expectedAmount, claim.Amount) - } - return nil -} - -// kavaClaimExists checks the store for a ukava claim -func (suite *DelegatorRewardsTestSuite) kavaClaimExists(ctx sdk.Context, owner sdk.AccAddress) bool { - _, found := suite.keeper.GetClaim(ctx, owner, "ukava", types.Stake) - return found -} - -func hardGenesisState(rewardRate sdk.Coin) app.GenesisState { - loanToValue := sdk.MustNewDecFromStr("0.6") - genState := types.NewGenesisState( - types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule( - true, - "bnb", - time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), - time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), - rewardRate, - time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), - types.Multipliers{ - types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), - types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), - types.NewMultiplier(types.Large, 24, sdk.OneDec()), - }, - ), - }, - types.DelegatorDistributionSchedules{ - types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule( - true, - "ukava", - time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), - time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), - rewardRate, - time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), - types.Multipliers{ - types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), - types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), - types.NewMultiplier(types.Large, 24, sdk.OneDec()), - }, - ), - time.Hour*24, - ), - }, - types.MoneyMarkets{ - types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), - }, - 0, // LTV counter - ), - types.DefaultPreviousBlockTime, - types.DefaultDistributionTimes, - ) - return app.GenesisState{ - types.ModuleName: types.ModuleCdc.MustMarshalJSON(genState), - } - -} - -func stakingGenesisState() app.GenesisState { - genState := staking.DefaultGenesisState() - genState.Params.BondDenom = "ukava" - return app.GenesisState{ - staking.ModuleName: staking.ModuleCdc.MustMarshalJSON(genState), - } -} - -// equalCoinsAuthGenState returns an auth genesis state with the same coins for each account -func equalCoinsAuthGenState(addresses []sdk.AccAddress, coins sdk.Coins) app.GenesisState { - coinsList := []sdk.Coins{} - for range addresses { - coinsList = append(coinsList, coins) - } - return app.NewAuthGenState(addresses, coinsList) -} diff --git a/x/hard/keeper/timelock_test.go b/x/hard/keeper/timelock_test.go index 16e0f393..e6513cf1 100644 --- a/x/hard/keeper/timelock_test.go +++ b/x/hard/keeper/timelock_test.go @@ -282,20 +282,12 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() { loanToValue := sdk.MustNewDecFromStr("0.6") hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) if tc.args.accArgs.vestingAccountBefore { ak := tApp.GetAccountKeeper() diff --git a/x/hard/keeper/withdraw.go b/x/hard/keeper/withdraw.go index 33cda8fc..d8c5f892 100644 --- a/x/hard/keeper/withdraw.go +++ b/x/hard/keeper/withdraw.go @@ -15,13 +15,15 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co return err } - k.SyncBorrowInterest(ctx, depositor) - k.SyncSupplyInterest(ctx, depositor) - deposit, found := k.GetDeposit(ctx, depositor) if !found { return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor) } + // Call incentive hook + k.BeforeDepositModified(ctx, deposit) + + k.SyncBorrowInterest(ctx, depositor) + k.SyncSupplyInterest(ctx, depositor) amount, err := k.CalculateWithdrawAmount(deposit.Amount, coins) if err != nil { @@ -47,19 +49,7 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co return err } - if deposit.Amount.IsEqual(amount) { - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeDeleteHardDeposit, - sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()), - ), - ) - k.DeleteDeposit(ctx, deposit) - return nil - } - deposit.Amount = deposit.Amount.Sub(amount) - newLtv, err := k.CalculateLtv(ctx, deposit, borrow) if err != nil { return err @@ -69,6 +59,9 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co // Update total supplied amount k.DecrementBorrowedCoins(ctx, amount) + // Call incentive hook + k.AfterDepositModified(ctx, deposit) + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeHardWithdrawal, @@ -76,7 +69,6 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()), ), ) - return nil } diff --git a/x/hard/keeper/withdraw_test.go b/x/hard/keeper/withdraw_test.go index 74654d58..f25d0ded 100644 --- a/x/hard/keeper/withdraw_test.go +++ b/x/hard/keeper/withdraw_test.go @@ -125,21 +125,13 @@ func (suite *KeeperTestSuite) TestWithdraw() { loanToValue := sdk.MustNewDecFromStr("0.6") hardGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ @@ -273,15 +265,6 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() { // Harvest module genesis state harvestGS := types.NewGenesisState(types.NewParams( true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, types.MoneyMarkets{ types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit @@ -301,7 +284,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() { sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, 0, // LTV counter - ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) + ), types.DefaultPreviousBlockTime) // Pricefeed module genesis state pricefeedGS := pricefeed.GenesisState{ diff --git a/x/hard/simulation/decoder.go b/x/hard/simulation/decoder.go index 63469e31..5db6af34 100644 --- a/x/hard/simulation/decoder.go +++ b/x/hard/simulation/decoder.go @@ -15,21 +15,16 @@ import ( // DecodeStore unmarshals the KVPair's Value to the corresponding hard type func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { - case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey), bytes.Equal(kvA.Key[:1], types.PreviousDelegationDistributionKey): - var timeA, timeB time.Time - cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) - cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) - return fmt.Sprintf("%s\n%s", timeA, timeB) case bytes.Equal(kvA.Key[:1], types.DepositsKeyPrefix): var depA, depB types.Deposit cdc.MustUnmarshalBinaryBare(kvA.Value, &depA) cdc.MustUnmarshalBinaryBare(kvB.Value, &depB) return fmt.Sprintf("%s\n%s", depA, depB) - case bytes.Equal(kvA.Key[:1], types.ClaimsKeyPrefix): - var claimA, claimB types.Claim - cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA) - cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB) - return fmt.Sprintf("%s\n%s", claimA, claimB) + case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey): + var timeA, timeB time.Time + cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) + return fmt.Sprintf("%s\n%s", timeA, timeB) default: panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) } diff --git a/x/hard/simulation/decoder_test.go b/x/hard/simulation/decoder_test.go index 21cae5cd..a9133dfb 100644 --- a/x/hard/simulation/decoder_test.go +++ b/x/hard/simulation/decoder_test.go @@ -27,16 +27,11 @@ func TestDecodeDistributionStore(t *testing.T) { cdc := makeTestCodec() prevBlockTime := time.Now().UTC() - deposit := types.NewDeposit(sdk.AccAddress("test"), - sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))), - types.SupplyInterestFactors{types.NewSupplyInterestFactor("bnb", sdk.OneDec())}) - claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "stake") + deposit := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))), types.SupplyInterestFactors{}) kvPairs := kv.Pairs{ kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, - kv.Pair{Key: []byte(types.PreviousDelegationDistributionKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, kv.Pair{Key: []byte(types.DepositsKeyPrefix), Value: cdc.MustMarshalBinaryBare(deposit)}, - kv.Pair{Key: []byte(types.ClaimsKeyPrefix), Value: cdc.MustMarshalBinaryBare(claim)}, kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } @@ -45,9 +40,7 @@ func TestDecodeDistributionStore(t *testing.T) { expectedLog string }{ {"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)}, - {"PreviousDistributionTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)}, {"Deposit", fmt.Sprintf("%s\n%s", deposit, deposit)}, - {"Claim", fmt.Sprintf("%s\n%s", claim, claim)}, {"other", ""}, } for i, tt := range tests { diff --git a/x/hard/types/claim.go b/x/hard/types/claim.go deleted file mode 100644 index 8c20ba68..00000000 --- a/x/hard/types/claim.go +++ /dev/null @@ -1,23 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Claim defines an amount of coins that the owner can claim -type Claim struct { - Owner sdk.AccAddress `json:"owner" yaml:"owner"` - DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"` - Amount sdk.Coin `json:"amount" yaml:"amount"` - Type ClaimType `json:"claim_type" yaml:"claim_type"` -} - -// NewClaim returns a new claim -func NewClaim(owner sdk.AccAddress, denom string, amount sdk.Coin, claimType ClaimType) Claim { - return Claim{ - Owner: owner, - DepositDenom: denom, - Amount: amount, - Type: claimType, - } -} diff --git a/x/hard/types/codec.go b/x/hard/types/codec.go index ea0c1868..e8edf358 100644 --- a/x/hard/types/codec.go +++ b/x/hard/types/codec.go @@ -20,5 +20,4 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgBorrow{}, "hard/MsgBorrow", nil) cdc.RegisterConcrete(MsgLiquidate{}, "hard/MsgLiquidate", nil) cdc.RegisterConcrete(MsgRepay{}, "hard/MsgRepay", nil) - cdc.RegisterConcrete(DistributionSchedule{}, "hard/DistributionSchedule", nil) } diff --git a/x/hard/types/expected_keepers.go b/x/hard/types/expected_keepers.go index b19e4475..ff5f2a16 100644 --- a/x/hard/types/expected_keepers.go +++ b/x/hard/types/expected_keepers.go @@ -45,3 +45,13 @@ type PricefeedKeeper interface { type AuctionKeeper interface { StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, error) } + +// HARDHooks event hooks for other keepers to run code in response to HARD modifications +type HARDHooks interface { + AfterDepositCreated(ctx sdk.Context, deposit Deposit) + BeforeDepositModified(ctx sdk.Context, deposit Deposit) + AfterDepositModified(ctx sdk.Context, deposit Deposit) + AfterBorrowCreated(ctx sdk.Context, borrow Borrow) + BeforeBorrowModified(ctx sdk.Context, borrow Borrow) + AfterBorrowModified(ctx sdk.Context, borrow Borrow) +} diff --git a/x/hard/types/genesis.go b/x/hard/types/genesis.go index 2be5884e..303204a9 100644 --- a/x/hard/types/genesis.go +++ b/x/hard/types/genesis.go @@ -5,39 +5,33 @@ import ( "fmt" "time" - sdk "github.com/cosmos/cosmos-sdk/types" - tmtime "github.com/tendermint/tendermint/types/time" ) // GenesisState default values var ( DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) - DefaultDistributionTimes = GenesisDistributionTimes{} ) // 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"` - PreviousDistributionTimes GenesisDistributionTimes `json:"previous_distribution_times" yaml:"previous_distribution_times"` + Params Params `json:"params" yaml:"params"` + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` } // NewGenesisState returns a new genesis state -func NewGenesisState(params Params, previousBlockTime time.Time, previousDistTimes GenesisDistributionTimes) GenesisState { +func NewGenesisState(params Params, previousBlockTime time.Time) GenesisState { return GenesisState{ - Params: params, - PreviousBlockTime: previousBlockTime, - PreviousDistributionTimes: previousDistTimes, + Params: params, + PreviousBlockTime: previousBlockTime, } } // DefaultGenesisState returns a default genesis state func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), - PreviousBlockTime: DefaultPreviousBlockTime, - PreviousDistributionTimes: DefaultDistributionTimes, + Params: DefaultParams(), + PreviousBlockTime: DefaultPreviousBlockTime, } } @@ -51,14 +45,6 @@ func (gs GenesisState) Validate() error { if gs.PreviousBlockTime.Equal(time.Time{}) { return fmt.Errorf("previous block time not set") } - for _, gdt := range gs.PreviousDistributionTimes { - if gdt.PreviousDistributionTime.Equal(time.Time{}) { - return fmt.Errorf("previous distribution time not set for %s", gdt.Denom) - } - if err := sdk.ValidateDenom(gdt.Denom); err != nil { - return err - } - } return nil } @@ -73,12 +59,3 @@ func (gs GenesisState) Equal(gs2 GenesisState) bool { func (gs GenesisState) IsEmpty() bool { return gs.Equal(GenesisState{}) } - -// GenesisDistributionTime stores the previous distribution time and its corresponding denom -type GenesisDistributionTime struct { - Denom string `json:"denom" yaml:"denom"` - PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"` -} - -// GenesisDistributionTimes slice of GenesisDistributionTime -type GenesisDistributionTimes []GenesisDistributionTime diff --git a/x/hard/types/genesis_test.go b/x/hard/types/genesis_test.go index 6eed15bd..bbf00057 100644 --- a/x/hard/types/genesis_test.go +++ b/x/hard/types/genesis_test.go @@ -5,8 +5,6 @@ import ( "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" "github.com/kava-labs/kava/x/hard/types" @@ -20,7 +18,6 @@ func (suite *GenesisTestSuite) TestGenesisValidation() { type args struct { params types.Params pbt time.Time - pdts types.GenesisDistributionTimes } testCases := []struct { name string @@ -33,7 +30,6 @@ func (suite *GenesisTestSuite) TestGenesisValidation() { args: args{ params: types.DefaultParams(), pbt: types.DefaultPreviousBlockTime, - pdts: types.DefaultDistributionTimes, }, expectPass: true, expectedErr: "", @@ -41,23 +37,8 @@ func (suite *GenesisTestSuite) TestGenesisValidation() { { name: "valid", args: args{ - params: types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - types.DefaultMoneyMarkets, - types.DefaultCheckLtvIndexCount, - ), - pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), - pdts: types.GenesisDistributionTimes{ - {PreviousDistributionTime: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), Denom: "bnb"}, - }, + params: types.NewParams(true, types.DefaultMoneyMarkets, types.DefaultCheckLtvIndexCount), + pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), }, expectPass: true, expectedErr: "", @@ -65,55 +46,16 @@ func (suite *GenesisTestSuite) TestGenesisValidation() { { name: "invalid previous blocktime", args: args{ - params: types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - types.DefaultMoneyMarkets, - types.DefaultCheckLtvIndexCount, - ), - pbt: time.Time{}, - pdts: types.GenesisDistributionTimes{ - {PreviousDistributionTime: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), Denom: "bnb"}, - }, + params: types.NewParams(true, types.DefaultMoneyMarkets, types.DefaultCheckLtvIndexCount), + pbt: time.Time{}, }, expectPass: false, expectedErr: "previous block time not set", }, - { - name: "invalid previous distribution time", - args: args{ - params: types.NewParams( - true, - types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}), - }, - types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - types.DefaultMoneyMarkets, - types.DefaultCheckLtvIndexCount, - ), - pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), - pdts: types.GenesisDistributionTimes{ - {PreviousDistributionTime: time.Time{}, Denom: "bnb"}, - }, - }, - expectPass: false, - expectedErr: "previous distribution time not set", - }, } for _, tc := range testCases { suite.Run(tc.name, func() { - gs := types.NewGenesisState(tc.args.params, tc.args.pbt, tc.args.pdts) + gs := types.NewGenesisState(tc.args.params, tc.args.pbt) err := gs.Validate() if tc.expectPass { suite.NoError(err) diff --git a/x/hard/types/hooks.go b/x/hard/types/hooks.go new file mode 100644 index 00000000..80260d87 --- /dev/null +++ b/x/hard/types/hooks.go @@ -0,0 +1,53 @@ +package types + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// MultiHARDHooks combine multiple HARD hooks, all hook functions are run in array sequence +type MultiHARDHooks []HARDHooks + +// NewMultiHARDHooks returns a new MultiHARDHooks +func NewMultiHARDHooks(hooks ...HARDHooks) MultiHARDHooks { + return hooks +} + +// AfterDepositCreated runs after a deposit is created +func (h MultiHARDHooks) AfterDepositCreated(ctx sdk.Context, deposit Deposit) { + for i := range h { + h[i].AfterDepositCreated(ctx, deposit) + } +} + +// BeforeDepositModified runs before a deposit is modified +func (h MultiHARDHooks) BeforeDepositModified(ctx sdk.Context, deposit Deposit) { + for i := range h { + h[i].BeforeDepositModified(ctx, deposit) + } +} + +// AfterDepositModified runs after a deposit is modified +func (h MultiHARDHooks) AfterDepositModified(ctx sdk.Context, deposit Deposit) { + for i := range h { + h[i].AfterDepositModified(ctx, deposit) + } +} + +// AfterBorrowCreated runs after a borrow is created +func (h MultiHARDHooks) AfterBorrowCreated(ctx sdk.Context, borrow Borrow) { + for i := range h { + h[i].AfterBorrowCreated(ctx, borrow) + } +} + +// BeforeBorrowModified runs before a borrow is modified +func (h MultiHARDHooks) BeforeBorrowModified(ctx sdk.Context, borrow Borrow) { + for i := range h { + h[i].BeforeBorrowModified(ctx, borrow) + } +} + +// AfterBorrowModified runs after a borrow is modified +func (h MultiHARDHooks) AfterBorrowModified(ctx sdk.Context, borrow Borrow) { + for i := range h { + h[i].AfterBorrowModified(ctx, borrow) + } +} diff --git a/x/hard/types/keys.go b/x/hard/types/keys.go index ccf20bb6..7d901975 100644 --- a/x/hard/types/keys.go +++ b/x/hard/types/keys.go @@ -34,20 +34,18 @@ const ( ) var ( - PreviousBlockTimeKey = []byte{0x01} - PreviousDelegationDistributionKey = []byte{0x02} - DepositsKeyPrefix = []byte{0x03} - ClaimsKeyPrefix = []byte{0x04} - BorrowsKeyPrefix = []byte{0x05} - BorrowedCoinsPrefix = []byte{0x06} - SuppliedCoinsPrefix = []byte{0x07} - MoneyMarketsPrefix = []byte{0x08} - PreviousAccrualTimePrefix = []byte{0x09} // denom -> time - TotalReservesPrefix = []byte{0x10} // denom -> sdk.Coin - BorrowInterestFactorPrefix = []byte{0x11} // denom -> sdk.Dec - SupplyInterestFactorPrefix = []byte{0x12} // denom -> sdk.Dec - LtvIndexPrefix = []byte{0x13} - sep = []byte(":") + PreviousBlockTimeKey = []byte{0x01} + DepositsKeyPrefix = []byte{0x03} + BorrowsKeyPrefix = []byte{0x05} + BorrowedCoinsPrefix = []byte{0x06} + SuppliedCoinsPrefix = []byte{0x07} + MoneyMarketsPrefix = []byte{0x08} + PreviousAccrualTimePrefix = []byte{0x09} // denom -> time + TotalReservesPrefix = []byte{0x10} // denom -> sdk.Coin + BorrowInterestFactorPrefix = []byte{0x11} // denom -> sdk.Dec + SupplyInterestFactorPrefix = []byte{0x12} // denom -> sdk.Dec + LtvIndexPrefix = []byte{0x13} + sep = []byte(":") ) // DepositTypeIteratorKey returns an interator prefix for interating over deposits by deposit denom @@ -55,16 +53,6 @@ func DepositTypeIteratorKey(denom string) []byte { return createKey([]byte(denom)) } -// ClaimKey key of a specific deposit in the store -func ClaimKey(depositType ClaimType, denom string, owner sdk.AccAddress) []byte { - return createKey([]byte(depositType), sep, []byte(denom), sep, owner) -} - -// ClaimTypeIteratorKey returns an interator prefix for interating over claims by deposit type and denom -func ClaimTypeIteratorKey(depositType ClaimType, denom string) []byte { - return createKey([]byte(depositType), sep, []byte(denom)) -} - // GetBorrowByLtvKey is used by the LTV index func GetBorrowByLtvKey(ltv sdk.Dec, borrower sdk.AccAddress) []byte { return append(ltv.Bytes(), borrower...) diff --git a/x/hard/types/params.go b/x/hard/types/params.go index ee839a95..4006e91f 100644 --- a/x/hard/types/params.go +++ b/x/hard/types/params.go @@ -1,9 +1,7 @@ package types import ( - "errors" "fmt" - "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" @@ -14,14 +12,9 @@ import ( // Parameter keys and default values var ( KeyActive = []byte("Active") - KeyLPSchedules = []byte("LPSchedules") - KeyDelegatorSchedule = []byte("DelegatorSchedule") KeyMoneyMarkets = []byte("MoneyMarkets") KeyCheckLtvIndexCount = []byte("CheckLtvIndexCount") DefaultActive = true - DefaultGovSchedules = DistributionSchedules{} - DefaultLPSchedules = DistributionSchedules{} - DefaultDelegatorSchedules = DelegatorDistributionSchedules{} DefaultMoneyMarkets = MoneyMarkets{} DefaultCheckLtvIndexCount = 10 GovDenom = cdptypes.DefaultGovDenom @@ -29,155 +22,9 @@ var ( // Params governance parameters for hard module type Params struct { - Active bool `json:"active" yaml:"active"` - LiquidityProviderSchedules DistributionSchedules `json:"liquidity_provider_schedules" yaml:"liquidity_provider_schedules"` - DelegatorDistributionSchedules DelegatorDistributionSchedules `json:"delegator_distribution_schedules" yaml:"delegator_distribution_schedules"` - MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"` - CheckLtvIndexCount int `json:"check_ltv_index_count" yaml:"check_ltv_index_count"` -} - -// DistributionSchedule distribution schedule for liquidity providers -type DistributionSchedule struct { - Active bool `json:"active" yaml:"active"` - DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"` - Start time.Time `json:"start" yaml:"start"` - End time.Time `json:"end" yaml:"end"` - RewardsPerSecond sdk.Coin `json:"rewards_per_second" yaml:"rewards_per_second"` - ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` - ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` -} - -// NewDistributionSchedule returns a new DistributionSchedule -func NewDistributionSchedule(active bool, denom string, start, end time.Time, reward sdk.Coin, claimEnd time.Time, multipliers Multipliers) DistributionSchedule { - return DistributionSchedule{ - Active: active, - DepositDenom: denom, - Start: start, - End: end, - RewardsPerSecond: reward, - ClaimEnd: claimEnd, - ClaimMultipliers: multipliers, - } -} - -// String implements fmt.Stringer -func (ds DistributionSchedule) String() string { - return fmt.Sprintf(`Liquidity Provider Distribution Schedule: - Deposit Denom: %s, - Start: %s, - End: %s, - Rewards Per Second: %s, - Claim End: %s, - Active: %t - `, ds.DepositDenom, ds.Start, ds.End, ds.RewardsPerSecond, ds.ClaimEnd, ds.Active) -} - -// Validate performs a basic check of a distribution schedule. -func (ds DistributionSchedule) Validate() error { - if !ds.RewardsPerSecond.IsValid() { - return fmt.Errorf("invalid reward coins %s for %s", ds.RewardsPerSecond, ds.DepositDenom) - } - if !ds.RewardsPerSecond.IsPositive() { - return fmt.Errorf("reward amount must be positive, is %s for %s", ds.RewardsPerSecond, ds.DepositDenom) - } - if ds.RewardsPerSecond.Denom != "hard" { - return fmt.Errorf("reward denom should be hard, is %s", ds.RewardsPerSecond.Denom) - } - if ds.Start.IsZero() { - return errors.New("reward period start time cannot be 0") - } - if ds.End.IsZero() { - return errors.New("reward period end time cannot be 0") - } - if ds.Start.After(ds.End) { - return fmt.Errorf("end period time %s cannot be before start time %s", ds.End, ds.Start) - } - if ds.ClaimEnd.Before(ds.End) { - return fmt.Errorf("claim end time %s cannot be before end time %s", ds.ClaimEnd, ds.End) - } - for _, multiplier := range ds.ClaimMultipliers { - if err := multiplier.Validate(); err != nil { - return err - } - } - return nil -} - -// DistributionSchedules slice of DistributionSchedule -type DistributionSchedules []DistributionSchedule - -// Validate checks if all the LiquidityProviderSchedules are valid and there are no duplicated -// entries. -func (dss DistributionSchedules) Validate() error { - seenPeriods := make(map[string]bool) - for _, ds := range dss { - if seenPeriods[ds.DepositDenom] { - return fmt.Errorf("duplicated distribution provider schedule with deposit denom %s", ds.DepositDenom) - } - - if err := ds.Validate(); err != nil { - return err - } - seenPeriods[ds.DepositDenom] = true - } - - return nil -} - -// String implements fmt.Stringer -func (dss DistributionSchedules) String() string { - out := "Distribution Schedules\n" - for _, ds := range dss { - out += fmt.Sprintf("%s\n", ds) - } - return out -} - -// DelegatorDistributionSchedule distribution schedule for delegators -type DelegatorDistributionSchedule struct { - DistributionSchedule DistributionSchedule `json:"distribution_schedule" yaml:"distribution_schedule"` - - DistributionFrequency time.Duration `json:"distribution_frequency" yaml:"distribution_frequency"` -} - -// NewDelegatorDistributionSchedule returns a new DelegatorDistributionSchedule -func NewDelegatorDistributionSchedule(ds DistributionSchedule, frequency time.Duration) DelegatorDistributionSchedule { - return DelegatorDistributionSchedule{ - DistributionSchedule: ds, - DistributionFrequency: frequency, - } -} - -// Validate performs a basic check of a reward fields. -func (dds DelegatorDistributionSchedule) Validate() error { - if err := dds.DistributionSchedule.Validate(); err != nil { - return err - } - if dds.DistributionFrequency <= 0 { - return fmt.Errorf("distribution frequency should be positive, got %d", dds.DistributionFrequency) - } - return nil -} - -// DelegatorDistributionSchedules slice of DelegatorDistributionSchedule -type DelegatorDistributionSchedules []DelegatorDistributionSchedule - -// Validate checks if all the LiquidityProviderSchedules are valid and there are no duplicated -// entries. -func (dds DelegatorDistributionSchedules) Validate() error { - seenPeriods := make(map[string]bool) - for _, ds := range dds { - if seenPeriods[ds.DistributionSchedule.DepositDenom] { - return fmt.Errorf("duplicated liquidity provider schedule with deposit denom %s", ds.DistributionSchedule.DepositDenom) - } - - if err := ds.Validate(); err != nil { - return err - } - seenPeriods[ds.DistributionSchedule.DepositDenom] = true - } - - return nil + Active bool `json:"active" yaml:"active"` + MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"` + CheckLtvIndexCount int `json:"check_ltv_index_count" yaml:"check_ltv_index_count"` } // Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked @@ -211,16 +58,6 @@ func (m Multiplier) Validate() error { return nil } -// GetMultiplier returns the named multiplier from the input distribution schedule -func (ds DistributionSchedule) GetMultiplier(name MultiplierName) (Multiplier, bool) { - for _, multiplier := range ds.ClaimMultipliers { - if multiplier.Name == name { - return multiplier, true - } - } - return Multiplier{}, false -} - // Multipliers slice of Multiplier type Multipliers []Multiplier @@ -426,33 +263,26 @@ func (irm InterestRateModel) Equal(irmCompareTo InterestRateModel) bool { type InterestRateModels []InterestRateModel // NewParams returns a new params object -func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistributionSchedules, - moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params { +func NewParams(active bool, moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params { return Params{ - Active: active, - LiquidityProviderSchedules: lps, - DelegatorDistributionSchedules: dds, - MoneyMarkets: moneyMarkets, - CheckLtvIndexCount: checkLtvIndexCount, + Active: active, + MoneyMarkets: moneyMarkets, + CheckLtvIndexCount: checkLtvIndexCount, } } // DefaultParams returns default params for hard module func DefaultParams() Params { - return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules, - DefaultMoneyMarkets, DefaultCheckLtvIndexCount) + return NewParams(DefaultActive, DefaultMoneyMarkets, DefaultCheckLtvIndexCount) } // String implements fmt.Stringer func (p Params) String() string { return fmt.Sprintf(`Params: Active: %t - Liquidity Provider Distribution Schedules %s - Delegator Distribution Schedule %s Money Markets %v Check LTV Index Count: %v`, - p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules, - p.MoneyMarkets, p.CheckLtvIndexCount) + p.Active, p.MoneyMarkets, p.CheckLtvIndexCount) } // ParamKeyTable Key declaration for parameters @@ -464,8 +294,6 @@ func ParamKeyTable() params.KeyTable { func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam), - params.NewParamSetPair(KeyLPSchedules, &p.LiquidityProviderSchedules, validateLPParams), - params.NewParamSetPair(KeyDelegatorSchedule, &p.DelegatorDistributionSchedules, validateDelegatorParams), params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams), params.NewParamSetPair(KeyCheckLtvIndexCount, &p.CheckLtvIndexCount, validateCheckLtvIndexCount), } @@ -477,14 +305,6 @@ func (p Params) Validate() error { return err } - if err := validateDelegatorParams(p.DelegatorDistributionSchedules); err != nil { - return err - } - - if err := validateLPParams(p.LiquidityProviderSchedules); err != nil { - return err - } - if err := validateMoneyMarketParams(p.MoneyMarkets); err != nil { return err } @@ -501,31 +321,6 @@ func validateActiveParam(i interface{}) error { return nil } -func validateLPParams(i interface{}) error { - dss, ok := i.(DistributionSchedules) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - for _, ds := range dss { - err := ds.Validate() - if err != nil { - return err - } - } - - return nil -} - -func validateDelegatorParams(i interface{}) error { - dds, ok := i.(DelegatorDistributionSchedules) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - return dds.Validate() -} - func validateMoneyMarketParams(i interface{}) error { mm, ok := i.(MoneyMarkets) if !ok { diff --git a/x/hard/types/params_test.go b/x/hard/types/params_test.go index 33a5ddfc..3aca53cd 100644 --- a/x/hard/types/params_test.go +++ b/x/hard/types/params_test.go @@ -3,12 +3,9 @@ package types_test import ( "strings" "testing" - "time" "github.com/stretchr/testify/suite" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/hard/types" ) @@ -18,9 +15,6 @@ type ParamTestSuite struct { func (suite *ParamTestSuite) TestParamValidation() { type args struct { - lps types.DistributionSchedules - gds types.DistributionSchedules - dds types.DelegatorDistributionSchedules mms types.MoneyMarkets ltvCounter int active bool @@ -34,8 +28,6 @@ func (suite *ParamTestSuite) TestParamValidation() { { name: "default", args: args{ - lps: types.DefaultLPSchedules, - dds: types.DefaultDelegatorSchedules, active: types.DefaultActive, }, expectPass: true, @@ -44,14 +36,6 @@ func (suite *ParamTestSuite) TestParamValidation() { { name: "valid", args: args{ - lps: types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - dds: types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, mms: types.DefaultMoneyMarkets, ltvCounter: 10, active: true, @@ -59,28 +43,10 @@ func (suite *ParamTestSuite) TestParamValidation() { expectPass: true, expectedErr: "", }, - { - name: "invalid rewards", - args: args{ - lps: types.DistributionSchedules{ - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("busd", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - }, - dds: types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( - types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), - time.Hour*24, - ), - }, - mms: types.DefaultMoneyMarkets, - ltvCounter: 10, - active: true, - }, - expectPass: false, - expectedErr: "reward denom should be hard", - }, } for _, tc := range testCases { suite.Run(tc.name, func() { - params := types.NewParams(tc.args.active, tc.args.lps, tc.args.dds, tc.args.mms, tc.args.ltvCounter) + params := types.NewParams(tc.args.active, tc.args.mms, tc.args.ltvCounter) err := params.Validate() if tc.expectPass { suite.NoError(err) diff --git a/x/incentive/abci.go b/x/incentive/abci.go index 0b4b4ede..89866bf4 100644 --- a/x/incentive/abci.go +++ b/x/incentive/abci.go @@ -8,8 +8,21 @@ import ( // BeginBlocker runs at the start of every block func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { - for _, rp := range k.GetParams(ctx).RewardPeriods { - err := k.AccumulateRewards(ctx, rp) + params := k.GetParams(ctx) + for _, rp := range params.USDXMintingRewardPeriods { + err := k.AccumulateUSDXMintingRewards(ctx, rp) + if err != nil { + panic(err) + } + } + for _, rp := range params.HardSupplyRewardPeriods { + err := k.AccumulateHardSupplyRewards(ctx, rp) + if err != nil { + panic(err) + } + } + for _, rp := range params.HardBorrowRewardPeriods { + err := k.AccumulateHardBorrowRewards(ctx, rp) if err != nil { panic(err) } diff --git a/x/incentive/alias.go b/x/incentive/alias.go index 0c728b3b..29f75940 100644 --- a/x/incentive/alias.go +++ b/x/incentive/alias.go @@ -55,33 +55,32 @@ var ( RegisterCodec = types.RegisterCodec // variable aliases - BlockTimeKey = types.BlockTimeKey - ClaimKeyPrefix = types.ClaimKeyPrefix - DefaultActive = types.DefaultActive - DefaultClaimEnd = types.DefaultClaimEnd - DefaultClaims = types.DefaultClaims - DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes - DefaultMultipliers = types.DefaultMultipliers - DefaultRewardPeriods = types.DefaultRewardPeriods - ErrAccountNotFound = types.ErrAccountNotFound - ErrClaimExpired = types.ErrClaimExpired - ErrClaimNotFound = types.ErrClaimNotFound - ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance - ErrInvalidAccountType = types.ErrInvalidAccountType - ErrInvalidMultiplier = types.ErrInvalidMultiplier - ErrNoClaimsFound = types.ErrNoClaimsFound - ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound - ErrZeroClaim = types.ErrZeroClaim - GovDenom = types.GovDenom - IncentiveMacc = types.IncentiveMacc - KeyActive = types.KeyActive - KeyClaimEnd = types.KeyClaimEnd - KeyMultipliers = types.KeyMultipliers - KeyRewards = types.KeyRewards - ModuleCdc = types.ModuleCdc - PrincipalDenom = types.PrincipalDenom - RewardFactorKey = types.RewardFactorKey - USDXMintingRewardDenom = types.USDXMintingRewardDenom + PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix + USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix + DefaultActive = types.DefaultActive + DefaultClaimEnd = types.DefaultClaimEnd + DefaultClaims = types.DefaultClaims + DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes + DefaultMultipliers = types.DefaultMultipliers + DefaultRewardPeriods = types.DefaultRewardPeriods + ErrAccountNotFound = types.ErrAccountNotFound + ErrClaimExpired = types.ErrClaimExpired + ErrClaimNotFound = types.ErrClaimNotFound + ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance + ErrInvalidAccountType = types.ErrInvalidAccountType + ErrInvalidMultiplier = types.ErrInvalidMultiplier + ErrNoClaimsFound = types.ErrNoClaimsFound + ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound + ErrZeroClaim = types.ErrZeroClaim + GovDenom = types.GovDenom + IncentiveMacc = types.IncentiveMacc + KeyClaimEnd = types.KeyClaimEnd + KeyMultipliers = types.KeyMultipliers + KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods + ModuleCdc = types.ModuleCdc + PrincipalDenom = types.PrincipalDenom + USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix + USDXMintingRewardDenom = types.USDXMintingRewardDenom ) type ( @@ -89,6 +88,7 @@ type ( Keeper = keeper.Keeper AccountKeeper = types.AccountKeeper CDPHooks = types.CDPHooks + HARDHooks = types.HARDHooks CdpKeeper = types.CdpKeeper GenesisAccumulationTime = types.GenesisAccumulationTime GenesisAccumulationTimes = types.GenesisAccumulationTimes diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go index 888cfdf0..1c19faec 100644 --- a/x/incentive/genesis.go +++ b/x/incentive/genesis.go @@ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) } - for _, rp := range gs.Params.RewardPeriods { + for _, rp := range gs.Params.USDXMintingRewardPeriods { _, found := cdpKeeper.GetCollateral(ctx, rp.CollateralType) if !found { panic(fmt.Sprintf("usdx minting collateral type %s not found in cdp collateral types", rp.CollateralType)) @@ -31,13 +31,15 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep k.SetParams(ctx, gs.Params) + // TODO: previous hard module accrual times/indexes should be set here + for _, gat := range gs.PreviousAccumulationTimes { - k.SetPreviousAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime) - k.SetRewardFactor(ctx, gat.CollateralType, gat.RewardFactor) + k.SetPreviousUSDXMintingAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime) + k.SetUSDXMintingRewardFactor(ctx, gat.CollateralType, gat.RewardFactor) } for _, claim := range gs.USDXMintingClaims { - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) } } @@ -46,16 +48,16 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { params := k.GetParams(ctx) - claims := k.GetAllClaims(ctx) + claims := k.GetAllUSDXMintingClaims(ctx) var gats GenesisAccumulationTimes - for _, rp := range params.RewardPeriods { - pat, found := k.GetPreviousAccrualTime(ctx, rp.CollateralType) + for _, rp := range params.USDXMintingRewardPeriods { + pat, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rp.CollateralType) if !found { pat = ctx.BlockTime() } - factor, found := k.GetRewardFactor(ctx, rp.CollateralType) + factor, found := k.GetUSDXMintingRewardFactor(ctx, rp.CollateralType) if !found { factor = sdk.ZeroDec() } diff --git a/x/incentive/handler_test.go b/x/incentive/handler_test.go index 7af7e2c3..c51e2fd3 100644 --- a/x/incentive/handler_test.go +++ b/x/incentive/handler_test.go @@ -44,6 +44,9 @@ func (suite *HandlerTestSuite) SetupTest() { authGS := app.NewAuthGenState(addrs, coins) incentiveGS := incentive.NewGenesisState( incentive.NewParams( + incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))}, + incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))}, + incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))}, incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))}, incentive.Multipliers{incentive.NewMultiplier(incentive.MultiplierName("small"), 1, d("0.25")), incentive.NewMultiplier(incentive.MultiplierName("large"), 12, d("1.0"))}, time.Date(2025, 12, 15, 14, 0, 0, 0, time.UTC), @@ -66,7 +69,7 @@ func (suite *HandlerTestSuite) addClaim() { suite.Require().NoError(err) c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())}) suite.NotPanics(func() { - suite.keeper.SetClaim(suite.ctx, c1) + suite.keeper.SetUSDXMintingClaim(suite.ctx, c1) }) } diff --git a/x/incentive/integration_test.go b/x/incentive/integration_test.go index a31e4c30..11b79d9f 100644 --- a/x/incentive/integration_test.go +++ b/x/incentive/integration_test.go @@ -8,6 +8,7 @@ import ( "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/incentive" + "github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/pricefeed" ) @@ -155,6 +156,9 @@ func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods .. genesis := incentive.NewGenesisState( incentive.NewParams( rewardPeriods, + types.RewardPeriods{}, + types.RewardPeriods{}, + types.RewardPeriods{}, incentive.Multipliers{ incentive.NewMultiplier(incentive.Small, 1, d("0.25")), incentive.NewMultiplier(incentive.Large, 12, d("1.0")), diff --git a/x/incentive/keeper/hooks.go b/x/incentive/keeper/hooks.go index 0482dd5e..b1468d11 100644 --- a/x/incentive/keeper/hooks.go +++ b/x/incentive/keeper/hooks.go @@ -3,6 +3,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" cdptypes "github.com/kava-labs/kava/x/cdp/types" + hardtypes "github.com/kava-labs/kava/x/hard/types" ) // Hooks wrapper struct for hooks @@ -11,18 +12,49 @@ type Hooks struct { } var _ cdptypes.CDPHooks = Hooks{} +var _ hardtypes.HARDHooks = Hooks{} // Hooks create new incentive hooks func (k Keeper) Hooks() Hooks { return Hooks{k} } // AfterCDPCreated function that runs after a cdp is created func (h Hooks) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) { - h.k.InitializeClaim(ctx, cdp) + h.k.InitializeUSDXMintingClaim(ctx, cdp) } // BeforeCDPModified function that runs before a cdp is modified // note that this is called immediately after interest is synchronized, and so could potentially // be called AfterCDPInterestUpdated or something like that, if we we're to expand the scope of cdp hooks func (h Hooks) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) { - h.k.SynchronizeReward(ctx, cdp) + h.k.SynchronizeUSDXMintingReward(ctx, cdp) +} + +// AfterDepositCreated function that runs after a deposit is created +func (h Hooks) AfterDepositCreated(ctx sdk.Context, deposit hardtypes.Deposit) { + h.k.InitializeHardSupplyReward(ctx, deposit) +} + +// BeforeDepositModified function that runs before a deposit is modified +func (h Hooks) BeforeDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) { + h.k.SynchronizeHardSupplyReward(ctx, deposit) +} + +// AfterDepositModified function that runs after a deposit is modified +func (h Hooks) AfterDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) { + h.k.UpdateHardSupplyIndexDenoms(ctx, deposit) +} + +// AfterBorrowCreated function that runs after a borrow is created +func (h Hooks) AfterBorrowCreated(ctx sdk.Context, borrow hardtypes.Borrow) { + h.k.InitializeHardBorrowReward(ctx, borrow) +} + +// BeforeBorrowModified function that runs before a borrow is modified +func (h Hooks) BeforeBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) { + h.k.SynchronizeHardBorrowReward(ctx, borrow) +} + +// AfterBorrowModified function that runs after a borrow is modified +func (h Hooks) AfterBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) { + h.k.UpdateHardBorrowIndexDenoms(ctx, borrow) } diff --git a/x/incentive/keeper/integration_test.go b/x/incentive/keeper/integration_test.go index cdc3f774..06204b67 100644 --- a/x/incentive/keeper/integration_test.go +++ b/x/incentive/keeper/integration_test.go @@ -7,7 +7,7 @@ import ( "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/cdp" - "github.com/kava-labs/kava/x/incentive" + "github.com/kava-labs/kava/x/hard" "github.com/kava-labs/kava/x/pricefeed" ) @@ -104,6 +104,7 @@ func NewPricefeedGenStateMulti() app.GenesisState { pfGenesis := pricefeed.GenesisState{ Params: pricefeed.Params{ Markets: []pricefeed.Market{ + {MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, @@ -111,6 +112,12 @@ func NewPricefeedGenStateMulti() app.GenesisState { }, }, PostedPrices: []pricefeed.PostedPrice{ + { + MarketID: "kava:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("2.00"), + Expiry: time.Now().Add(1 * time.Hour), + }, { MarketID: "btc:usd", OracleAddress: sdk.AccAddress{}, @@ -140,73 +147,26 @@ func NewPricefeedGenStateMulti() app.GenesisState { return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} } -func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ...incentive.RewardPeriod) app.GenesisState { - var accumulationTimes incentive.GenesisAccumulationTimes - for _, rp := range rewardPeriods { - accumulationTimes = append( - accumulationTimes, - incentive.NewGenesisAccumulationTime( - rp.CollateralType, - previousAccumTime, - sdk.ZeroDec(), - ), - ) - } - genesis := incentive.NewGenesisState( - incentive.NewParams( - rewardPeriods, - incentive.Multipliers{ - incentive.NewMultiplier(incentive.Small, 1, d("0.25")), - incentive.NewMultiplier(incentive.Large, 12, d("1.0")), - }, - endTime, - ), - accumulationTimes, - incentive.USDXMintingClaims{}, - ) - return app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(genesis)} -} +func NewHardGenStateMulti() app.GenesisState { + KAVA_CF := int64(1000000) + USDX_CF := int64(1000000) + BNB_CF := int64(100000000) + BTCB_CF := int64(100000000) -func NewCDPGenStateHighInterest() app.GenesisState { - cdpGenesis := cdp.GenesisState{ - Params: cdp.Params{ - GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000), - SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, - SurplusAuctionLot: cdp.DefaultSurplusLot, - DebtAuctionThreshold: cdp.DefaultDebtThreshold, - DebtAuctionLot: cdp.DefaultDebtLot, - CollateralParams: cdp.CollateralParams{ - { - Denom: "bnb", - Type: "bnb-a", - LiquidationRatio: sdk.MustNewDecFromStr("1.5"), - DebtLimit: sdk.NewInt64Coin("usdx", 500000000000), - StabilityFee: sdk.MustNewDecFromStr("1.000000051034942716"), // 500% APR - LiquidationPenalty: d("0.05"), - AuctionSize: i(50000000000), - Prefix: 0x22, - SpotMarketID: "bnb:usd", - LiquidationMarketID: "bnb:usd", - ConversionFactor: i(8), - }, - }, - DebtParam: cdp.DebtParam{ - Denom: "usdx", - ReferenceAsset: "usd", - ConversionFactor: i(6), - DebtFloor: i(10000000), - }, + loanToValue, _ := sdk.NewDecFromStr("0.6") + borrowLimit := sdk.NewDec(1000000000000000) + + hardGS := hard.NewGenesisState(hard.NewParams( + true, + hard.MoneyMarkets{ + hard.NewMoneyMarket("usdx", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), + hard.NewMoneyMarket("ukava", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), + hard.NewMoneyMarket("bnb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "bnb:usd", sdk.NewInt(1000000), sdk.NewInt(BNB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), + hard.NewMoneyMarket("btcb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "btc:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), + hard.NewMoneyMarket("xrp", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "xrp:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, - StartingCdpID: cdp.DefaultCdpStartingID, - DebtDenom: cdp.DefaultDebtDenom, - GovDenom: cdp.DefaultGovDenom, - CDPs: cdp.CDPs{}, - PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{ - cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()), - }, - TotalPrincipals: cdp.GenesisTotalPrincipals{ - cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()), - }, - } - return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} + 0, // LTV counter + ), hard.DefaultPreviousBlockTime) + + return app.GenesisState{hard.ModuleName: hard.ModuleCdc.MustMarshalJSON(hardGS)} } diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 3a2f4020..4ddb1811 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -16,6 +16,7 @@ type Keeper struct { accountKeeper types.AccountKeeper cdc *codec.Codec cdpKeeper types.CdpKeeper + hardKeeper types.HardKeeper key sdk.StoreKey paramSubspace subspace.Subspace supplyKeeper types.SupplyKeeper @@ -24,22 +25,23 @@ type Keeper struct { // 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, + cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, ) Keeper { return Keeper{ accountKeeper: ak, cdc: cdc, cdpKeeper: cdpk, + hardKeeper: hk, key: key, paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), supplyKeeper: sk, } } -// GetClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found -func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintingClaim, bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) +// GetUSDXMintingClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found +func (k Keeper) GetUSDXMintingClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintingClaim, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix) bz := store.Get(addr) if bz == nil { return types.USDXMintingClaim{}, false @@ -49,23 +51,23 @@ func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintin return c, true } -// SetClaim sets the claim in the store corresponding to the input address, collateral type, and id -func (k Keeper) SetClaim(ctx sdk.Context, c types.USDXMintingClaim) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) +// SetUSDXMintingClaim sets the claim in the store corresponding to the input address, collateral type, and id +func (k Keeper) SetUSDXMintingClaim(ctx sdk.Context, c types.USDXMintingClaim) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix) bz := k.cdc.MustMarshalBinaryBare(c) store.Set(c.Owner, bz) } -// DeleteClaim deletes the claim in the store corresponding to the input address, collateral type, and id -func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) +// DeleteUSDXMintingClaim deletes the claim in the store corresponding to the input address, collateral type, and id +func (k Keeper) DeleteUSDXMintingClaim(ctx sdk.Context, owner sdk.AccAddress) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix) store.Delete(owner) } -// 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.USDXMintingClaim) (stop bool)) { - store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) +// IterateUSDXMintingClaims iterates over all claim objects in the store and preforms a callback function +func (k Keeper) IterateUSDXMintingClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix) iterator := sdk.KVStorePrefixIterator(store, []byte{}) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { @@ -77,19 +79,19 @@ func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim) } } -// GetAllClaims returns all Claim objects in the store -func (k Keeper) GetAllClaims(ctx sdk.Context) types.USDXMintingClaims { +// GetAllUSDXMintingClaims returns all Claim objects in the store +func (k Keeper) GetAllUSDXMintingClaims(ctx sdk.Context) types.USDXMintingClaims { cs := types.USDXMintingClaims{} - k.IterateClaims(ctx, func(c types.USDXMintingClaim) (stop bool) { + k.IterateUSDXMintingClaims(ctx, func(c types.USDXMintingClaim) (stop bool) { cs = append(cs, c) return false }) return cs } -// GetPreviousAccrualTime returns the last time a collateral type accrued rewards -func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) +// GetPreviousUSDXMintingAccrualTime returns the last time a collateral type accrued USDX minting rewards +func (k Keeper) GetPreviousUSDXMintingAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix) bz := store.Get([]byte(ctype)) if bz == nil { return time.Time{}, false @@ -98,15 +100,15 @@ func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime return blockTime, true } -// SetPreviousAccrualTime sets the last time a collateral type accrued rewards -func (k Keeper) SetPreviousAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) { - store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) +// SetPreviousUSDXMintingAccrualTime sets the last time a collateral type accrued USDX minting rewards +func (k Keeper) SetPreviousUSDXMintingAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix) store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(blockTime)) } -// IterateAccrualTimes iterates over all previous accrual times and preforms a callback function -func (k Keeper) IterateAccrualTimes(ctx sdk.Context, cb func(string, time.Time) (stop bool)) { - store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) +// IterateUSDXMintingAccrualTimes iterates over all previous USDX minting accrual times and preforms a callback function +func (k Keeper) IterateUSDXMintingAccrualTimes(ctx sdk.Context, cb func(string, time.Time) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix) iterator := sdk.KVStorePrefixIterator(store, []byte{}) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { @@ -120,9 +122,9 @@ func (k Keeper) IterateAccrualTimes(ctx sdk.Context, cb func(string, time.Time) } } -// GetRewardFactor returns the current reward factor for an individual collateral type -func (k Keeper) GetRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey) +// GetUSDXMintingRewardFactor returns the current reward factor for an individual collateral type +func (k Keeper) GetUSDXMintingRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingRewardFactorKeyPrefix) bz := store.Get([]byte(ctype)) if bz == nil { return sdk.ZeroDec(), false @@ -131,8 +133,146 @@ func (k Keeper) GetRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, return factor, true } -// SetRewardFactor sets the current reward factor for an individual collateral type -func (k Keeper) SetRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) { - store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey) +// SetUSDXMintingRewardFactor sets the current reward factor for an individual collateral type +func (k Keeper) SetUSDXMintingRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) { + store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingRewardFactorKeyPrefix) store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor)) } + +// GetHardLiquidityProviderClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found +func (k Keeper) GetHardLiquidityProviderClaim(ctx sdk.Context, addr sdk.AccAddress) (types.HardLiquidityProviderClaim, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix) + bz := store.Get(addr) + if bz == nil { + return types.HardLiquidityProviderClaim{}, false + } + var c types.HardLiquidityProviderClaim + k.cdc.MustUnmarshalBinaryBare(bz, &c) + return c, true +} + +// SetHardLiquidityProviderClaim sets the claim in the store corresponding to the input address, collateral type, and id +func (k Keeper) SetHardLiquidityProviderClaim(ctx sdk.Context, c types.HardLiquidityProviderClaim) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(c) + store.Set(c.Owner, bz) +} + +// DeleteHardLiquidityProviderClaim deletes the claim in the store corresponding to the input address, collateral type, and id +func (k Keeper) DeleteHardLiquidityProviderClaim(ctx sdk.Context, owner sdk.AccAddress) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix) + store.Delete(owner) +} + +// IterateHardLiquidityProviderClaims iterates over all claim objects in the store and preforms a callback function +func (k Keeper) IterateHardLiquidityProviderClaims(ctx sdk.Context, cb func(c types.HardLiquidityProviderClaim) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var c types.HardLiquidityProviderClaim + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c) + if cb(c) { + break + } + } +} + +// GetAllHardLiquidityProviderClaims returns all Claim objects in the store +func (k Keeper) GetAllHardLiquidityProviderClaims(ctx sdk.Context) types.HardLiquidityProviderClaims { + cs := types.HardLiquidityProviderClaims{} + k.IterateHardLiquidityProviderClaims(ctx, func(c types.HardLiquidityProviderClaim) (stop bool) { + cs = append(cs, c) + return false + }) + return cs +} + +// SetHardSupplyRewardFactor sets the current interest factor for an individual market +func (k Keeper) SetHardSupplyRewardFactor(ctx sdk.Context, denom string, borrowIndex sdk.Dec) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardFactorKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(borrowIndex) + store.Set([]byte(denom), bz) +} + +// GetHardSupplyRewardFactor returns the current interest factor for an individual market +func (k Keeper) GetHardSupplyRewardFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardFactorKeyPrefix) + bz := store.Get([]byte(denom)) + if bz == nil { + return sdk.ZeroDec(), false + } + var interestFactor sdk.Dec + k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor) + return interestFactor, true +} + +// SetHardBorrowRewardFactor sets the current interest factor for an individual market +func (k Keeper) SetHardBorrowRewardFactor(ctx sdk.Context, denom string, borrowIndex sdk.Dec) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardFactorKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(borrowIndex) + store.Set([]byte(denom), bz) +} + +// GetHardBorrowRewardFactor returns the current interest factor for an individual market +func (k Keeper) GetHardBorrowRewardFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardFactorKeyPrefix) + bz := store.Get([]byte(denom)) + if bz == nil { + return sdk.ZeroDec(), false + } + var interestFactor sdk.Dec + k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor) + return interestFactor, true +} + +// GetHardDelegatorRewardFactor returns the current reward factor for an individual collateral type +func (k Keeper) GetHardDelegatorRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardDelegatorRewardFactorKeyPrefix) + bz := store.Get([]byte(ctype)) + if bz == nil { + return sdk.ZeroDec(), false + } + k.cdc.MustUnmarshalBinaryBare(bz, &factor) + return factor, true +} + +// SetHardDelegatorRewardFactor sets the current reward factor for an individual collateral type +func (k Keeper) SetHardDelegatorRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) { + store := prefix.NewStore(ctx.KVStore(k.key), types.HardDelegatorRewardFactorKeyPrefix) + store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor)) +} + +// GetPreviousHardSupplyRewardAccrualTime returns the last time a denom accrued Hard protocol supply-side rewards +func (k Keeper) GetPreviousHardSupplyRewardAccrualTime(ctx sdk.Context, denom string) (blockTime time.Time, found bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardSupplyRewardAccrualTimeKeyPrefix) + bz := store.Get([]byte(denom)) + if bz == nil { + return time.Time{}, false + } + k.cdc.MustUnmarshalBinaryBare(bz, &blockTime) + return blockTime, true +} + +// SetPreviousHardSupplyRewardAccrualTime sets the last time a denom accrued Hard protocol supply-side rewards +func (k Keeper) SetPreviousHardSupplyRewardAccrualTime(ctx sdk.Context, denom string, blockTime time.Time) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardSupplyRewardAccrualTimeKeyPrefix) + store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime)) +} + +// GetPreviousHardBorrowRewardAccrualTime returns the last time a denom accrued Hard protocol borrow-side rewards +func (k Keeper) GetPreviousHardBorrowRewardAccrualTime(ctx sdk.Context, denom string) (blockTime time.Time, found bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardBorrowRewardAccrualTimeKeyPrefix) + bz := store.Get([]byte(denom)) + if bz == nil { + return time.Time{}, false + } + k.cdc.MustUnmarshalBinaryBare(bz, &blockTime) + return blockTime, true +} + +// SetPreviousHardBorrowRewardAccrualTime sets the last time a denom accrued Hard protocol borrow-side rewards +func (k Keeper) SetPreviousHardBorrowRewardAccrualTime(ctx sdk.Context, denom string, blockTime time.Time) { + store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardBorrowRewardAccrualTimeKeyPrefix) + store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime)) +} diff --git a/x/incentive/keeper/keeper_test.go b/x/incentive/keeper/keeper_test.go index 5d25a312..1e7c28ec 100644 --- a/x/incentive/keeper/keeper_test.go +++ b/x/incentive/keeper/keeper_test.go @@ -15,6 +15,7 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" "github.com/kava-labs/kava/app" + hardkeeper "github.com/kava-labs/kava/x/hard/keeper" "github.com/kava-labs/kava/x/incentive/keeper" "github.com/kava-labs/kava/x/incentive/types" ) @@ -23,10 +24,11 @@ import ( type KeeperTestSuite struct { suite.Suite - keeper keeper.Keeper - app app.TestApp - ctx sdk.Context - addrs []sdk.AccAddress + keeper keeper.Keeper + hardKeeper hardkeeper.Keeper + app app.TestApp + ctx sdk.Context + addrs []sdk.AccAddress } // The default state used by each test @@ -52,45 +54,41 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul return sk.GetModuleAccount(suite.ctx, name) } -func (suite *KeeperTestSuite) TestGetSetDeleteClaim() { +func (suite *KeeperTestSuite) TestGetSetDeleteUSDXMintingClaim() { c := types.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())}) - _, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + _, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().False(found) suite.Require().NotPanics(func() { - suite.keeper.SetClaim(suite.ctx, c) + suite.keeper.SetUSDXMintingClaim(suite.ctx, c) }) - testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + testC, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().True(found) suite.Require().Equal(c, testC) suite.Require().NotPanics(func() { - suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0]) + suite.keeper.DeleteUSDXMintingClaim(suite.ctx, suite.addrs[0]) }) - _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + _, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().False(found) } -func (suite *KeeperTestSuite) TestIterateClaims() { +func (suite *KeeperTestSuite) TestIterateUSDXMintingClaims() { for i := 0; i < len(suite.addrs); i++ { c := types.NewUSDXMintingClaim(suite.addrs[i], c("ukava", 100000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())}) suite.Require().NotPanics(func() { - suite.keeper.SetClaim(suite.ctx, c) + suite.keeper.SetUSDXMintingClaim(suite.ctx, c) }) } claims := types.USDXMintingClaims{} - suite.keeper.IterateClaims(suite.ctx, func(c types.USDXMintingClaim) bool { + suite.keeper.IterateUSDXMintingClaims(suite.ctx, func(c types.USDXMintingClaim) bool { claims = append(claims, c) return false }) suite.Require().Equal(len(suite.addrs), len(claims)) - claims = suite.keeper.GetAllClaims(suite.ctx) + claims = suite.keeper.GetAllUSDXMintingClaims(suite.ctx) suite.Require().Equal(len(suite.addrs), len(claims)) } -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - func createPeriodicVestingAccount(origVesting sdk.Coins, periods vesting.Periods, startTime, endTime int64) (*vesting.PeriodicVestingAccount, error) { _, addr := app.GeneratePrivKeyAddressPairs(1) bacc := auth.NewBaseAccountWithAddress(addr[0]) @@ -112,3 +110,7 @@ func i(in int64) sdk.Int { return sdk.NewInt(in) } func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/incentive/keeper/params.go b/x/incentive/keeper/params.go index a1306bcf..34c12dd3 100644 --- a/x/incentive/keeper/params.go +++ b/x/incentive/keeper/params.go @@ -20,10 +20,10 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSubspace.SetParamSet(ctx, ¶ms) } -// GetRewardPeriod returns the reward period with the specified collateral type if it's found in the params -func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) { +// GetUSDXMintingRewardPeriod returns the reward period with the specified collateral type if it's found in the params +func (k Keeper) GetUSDXMintingRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) { params := k.GetParams(ctx) - for _, rp := range params.RewardPeriods { + for _, rp := range params.USDXMintingRewardPeriods { if rp.CollateralType == collateralType { return rp, true } @@ -31,6 +31,28 @@ func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.R return types.RewardPeriod{}, false } +// GetHardSupplyRewardPeriod returns the reward period with the specified collateral type if it's found in the params +func (k Keeper) GetHardSupplyRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) { + params := k.GetParams(ctx) + for _, rp := range params.HardSupplyRewardPeriods { + if rp.CollateralType == denom { + return rp, true + } + } + return types.RewardPeriod{}, false +} + +// GetHardBorrowRewardPeriod returns the reward period with the specified collateral type if it's found in the params +func (k Keeper) GetHardBorrowRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) { + params := k.GetParams(ctx) + for _, rp := range params.HardBorrowRewardPeriods { + if rp.CollateralType == denom { + return rp, true + } + } + return types.RewardPeriod{}, false +} + // GetMultiplier returns the multiplier with the specified name if it's found in the params func (k Keeper) GetMultiplier(ctx sdk.Context, name types.MultiplierName) (types.Multiplier, bool) { params := k.GetParams(ctx) diff --git a/x/incentive/keeper/payout.go b/x/incentive/keeper/payout.go index cfc2c80e..b0067c50 100644 --- a/x/incentive/keeper/payout.go +++ b/x/incentive/keeper/payout.go @@ -14,7 +14,7 @@ import ( // ClaimReward sends the reward amount to the input address and zero's out the claim in the store func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error { - claim, found := k.GetClaim(ctx, addr) + claim, found := k.GetUSDXMintingClaim(ctx, addr) if !found { return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr) } diff --git a/x/incentive/keeper/payout_test.go b/x/incentive/keeper/payout_test.go index 7091a2b6..aa6fb0cd 100644 --- a/x/incentive/keeper/payout_test.go +++ b/x/incentive/keeper/payout_test.go @@ -86,18 +86,21 @@ func (suite *KeeperTestSuite) TestPayoutClaim() { } for _, tc := range testCases { suite.Run(tc.name, func() { - suite.SetupWithCDPGenState() + suite.SetupWithGenState() suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) // setup incentive state params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, tc.args.multipliers, tc.args.initialTime.Add(time.Hour*24*365*5), ) suite.keeper.SetParams(suite.ctx, params) - suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) - suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) + suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) + suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) // setup account state sk := suite.app.GetSupplyKeeper() @@ -115,15 +118,15 @@ func (suite *KeeperTestSuite) TestPayoutClaim() { err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) suite.Require().NoError(err) - claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().True(found) suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) - rewardPeriod, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.args.ctype) + rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype) suite.Require().True(found) - err = suite.keeper.AccumulateRewards(suite.ctx, rewardPeriod) + err = suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod) suite.Require().NoError(err) err = suite.keeper.ClaimReward(suite.ctx, suite.addrs[0], tc.args.multiplier) @@ -140,7 +143,7 @@ func (suite *KeeperTestSuite) TestPayoutClaim() { suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods) } - claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) fmt.Println(claim) suite.Require().True(found) suite.Require().Equal(c("ukava", 0), claim.Reward) diff --git a/x/incentive/keeper/querier.go b/x/incentive/keeper/querier.go index 915d1380..c0066d18 100644 --- a/x/incentive/keeper/querier.go +++ b/x/incentive/keeper/querier.go @@ -46,10 +46,10 @@ func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e } var claims types.USDXMintingClaims if len(requestParams.Owner) > 0 { - claim, _ := k.GetClaim(ctx, requestParams.Owner) + claim, _ := k.GetUSDXMintingClaim(ctx, requestParams.Owner) claims = append(claims, claim) } else { - claims = k.GetAllClaims(ctx) + claims = k.GetAllUSDXMintingClaims(ctx) } var paginatedClaims types.USDXMintingClaims diff --git a/x/incentive/keeper/rewards.go b/x/incentive/keeper/rewards.go index 0e5b27be..7a88c49b 100644 --- a/x/incentive/keeper/rewards.go +++ b/x/incentive/keeper/rewards.go @@ -1,24 +1,22 @@ package keeper import ( + "fmt" "math" "time" sdk "github.com/cosmos/cosmos-sdk/types" cdptypes "github.com/kava-labs/kava/x/cdp/types" + hardtypes "github.com/kava-labs/kava/x/hard/types" "github.com/kava-labs/kava/x/incentive/types" ) -// AccumulateRewards updates the rewards accumulated for the input reward period -func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { - if !rewardPeriod.Active { - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) - return nil - } - previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, rewardPeriod.CollateralType) +// AccumulateUSDXMintingRewards updates the rewards accumulated for the input reward period +func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { + previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType) if !found { - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) return nil } timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime) @@ -26,50 +24,135 @@ func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeri return nil } if rewardPeriod.RewardsPerSecond.Amount.IsZero() { - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) return nil } totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec() if totalPrincipal.IsZero() { - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) return nil } newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType) if !found { - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) return nil } rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal) - previousRewardFactor, found := k.GetRewardFactor(ctx, rewardPeriod.CollateralType) + previousRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType) if !found { previousRewardFactor = sdk.ZeroDec() } newRewardFactor := previousRewardFactor.Add(rewardFactor) - k.SetRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) - k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) + k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) return nil } -// InitializeClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost. +// AccumulateHardBorrowRewards updates the rewards accumulated for the input reward period +func (k Keeper) AccumulateHardBorrowRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { + previousAccrualTime, found := k.GetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType) + if !found { + k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime) + if timeElapsed.IsZero() { + return nil + } + if rewardPeriod.RewardsPerSecond.Amount.IsZero() { + k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + totalBorrowedCoins, foundTotalBorrowedCoins := k.hardKeeper.GetBorrowedCoins(ctx) + if foundTotalBorrowedCoins { + totalBorrowed := totalBorrowedCoins.AmountOf(rewardPeriod.CollateralType).ToDec() + if totalBorrowed.IsZero() { + k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) + hardFactor, found := k.hardKeeper.GetBorrowInterestFactor(ctx, rewardPeriod.CollateralType) + if !found { + k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + rewardFactor := newRewards.ToDec().Mul(hardFactor).Quo(totalBorrowed) + + previousRewardFactor, found := k.GetHardBorrowRewardFactor(ctx, rewardPeriod.CollateralType) + if !found { + previousRewardFactor = sdk.ZeroDec() + } + newRewardFactor := previousRewardFactor.Add(rewardFactor) + k.SetHardBorrowRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) + } + k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + + return nil +} + +// AccumulateHardSupplyRewards updates the rewards accumulated for the input reward period +func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { + previousAccrualTime, found := k.GetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType) + if !found { + k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime) + if timeElapsed.IsZero() { + return nil + } + if rewardPeriod.RewardsPerSecond.Amount.IsZero() { + k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + + totalSuppliedCoins, foundTotalSuppliedCoins := k.hardKeeper.GetSuppliedCoins(ctx) + if foundTotalSuppliedCoins { + totalSupplied := totalSuppliedCoins.AmountOf(rewardPeriod.CollateralType).ToDec() + if totalSupplied.IsZero() { + k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) + hardFactor, found := k.hardKeeper.GetSupplyInterestFactor(ctx, rewardPeriod.CollateralType) + if !found { + k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + return nil + } + rewardFactor := newRewards.ToDec().Mul(hardFactor).Quo(totalSupplied) + + previousRewardFactor, found := k.GetHardSupplyRewardFactor(ctx, rewardPeriod.CollateralType) + if !found { + previousRewardFactor = sdk.ZeroDec() + } + newRewardFactor := previousRewardFactor.Add(rewardFactor) + k.SetHardSupplyRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) + } + k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) + + return nil +} + +// InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost. // this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't // accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor, // any unclaimed rewards are preserved, but no new rewards are added. -func (k Keeper) InitializeClaim(ctx sdk.Context, cdp cdptypes.CDP) { - _, found := k.GetRewardPeriod(ctx, cdp.Type) +func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) { + _, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) if !found { // this collateral type is not incentivized, do nothing return } - rewardFactor, found := k.GetRewardFactor(ctx, cdp.Type) + rewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) if !found { rewardFactor = sdk.ZeroDec() } - claim, found := k.GetClaim(ctx, cdp.Owner) + claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) if !found { // this is the owner's first usdx minting reward claim claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)}) - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return } // the owner has an existing usdx minting reward claim @@ -79,26 +162,26 @@ func (k Keeper) InitializeClaim(ctx sdk.Context, cdp cdptypes.CDP) { } else { // the owner has a previous usdx minting reward for this collateral type claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor) } - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) } -// SynchronizeReward updates the claim object by adding any accumulated rewards and updating the reward index value. +// SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value. // this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module -func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) { - _, found := k.GetRewardPeriod(ctx, cdp.Type) +func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) { + _, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type) if !found { // this collateral type is not incentivized, do nothing return } - globalRewardFactor, found := k.GetRewardFactor(ctx, cdp.Type) + globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type) if !found { globalRewardFactor = sdk.ZeroDec() } - claim, found := k.GetClaim(ctx, cdp.Owner) + claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner) if !found { claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)}) - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return } @@ -106,7 +189,7 @@ func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) { index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor)) - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return } userRewardFactor := claim.RewardIndexes[index].RewardFactor @@ -115,21 +198,212 @@ func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) { return } claim.RewardIndexes[index].RewardFactor = globalRewardFactor - newRewardsAmount := cdp.GetTotalPrincipal().Amount.ToDec().Quo(cdp.InterestFactor).Mul(rewardsAccumulatedFactor).RoundInt() + newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt() if newRewardsAmount.IsZero() { - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return } newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount) claim.Reward = claim.Reward.Add(newRewardsCoin) - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return } +// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim +// by creating the claim and setting the supply reward factor index +func (k Keeper) InitializeHardSupplyReward(ctx sdk.Context, deposit hardtypes.Deposit) { + var supplyRewardIndexes types.RewardIndexes + for _, coin := range deposit.Amount { + _, rpFound := k.GetHardSupplyRewardPeriod(ctx, coin.Denom) + if !rpFound { + continue + } + + supplyFactor, foundSupplyFactor := k.GetHardSupplyRewardFactor(ctx, coin.Denom) + if !foundSupplyFactor { + supplyFactor = sdk.ZeroDec() + } + + supplyRewardIndexes = append(supplyRewardIndexes, types.NewRewardIndex(coin.Denom, supplyFactor)) + } + + claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor) + if found { + // Reset borrow reward indexes + claim.BorrowRewardIndexes = types.RewardIndexes{} + } else { + // Instantiate claim object + claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, + sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()), + nil, nil, nil) + } + + claim.SupplyRewardIndexes = supplyRewardIndexes + k.SetHardLiquidityProviderClaim(ctx, claim) +} + +// SynchronizeHardSupplyReward updates the claim object by adding any accumulated rewards +// and updating the reward index value +func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.Deposit) { + claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor) + if !found { + return + } + + for _, coin := range deposit.Amount { + supplyFactor, found := k.GetHardSupplyRewardFactor(ctx, coin.Denom) + if !found { + fmt.Printf("\n[LOG]: %s does not have a supply factor", coin.Denom) // TODO: remove before production + continue + } + + supplyIndex, hasSupplyRewardIndex := claim.HasSupplyRewardIndex(coin.Denom) + if !hasSupplyRewardIndex { + continue + } + + userRewardFactor := claim.SupplyRewardIndexes[supplyIndex].RewardFactor + rewardsAccumulatedFactor := supplyFactor.Sub(userRewardFactor) + if rewardsAccumulatedFactor.IsZero() { + continue + } + claim.SupplyRewardIndexes[supplyIndex].RewardFactor = supplyFactor + + newRewardsAmount := rewardsAccumulatedFactor.Mul(deposit.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() + if newRewardsAmount.IsZero() || newRewardsAmount.IsNegative() { + continue + } + + newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, newRewardsAmount) + claim.Reward = claim.Reward.Add(newRewardsCoin) + } + + k.SetHardLiquidityProviderClaim(ctx, claim) +} + +// InitializeHardBorrowReward initializes the borrow-side of a hard liquidity provider claim +// by creating the claim and setting the borrow reward factor index +func (k Keeper) InitializeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Borrow) { + claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower) + if !found { + claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, + sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()), + nil, nil, nil) + } + + var borrowRewardIndexes types.RewardIndexes + for _, coin := range borrow.Amount { + _, rpFound := k.GetHardBorrowRewardPeriod(ctx, coin.Denom) + if !rpFound { + continue + } + + borrowFactor, foundBorrowFactor := k.GetHardBorrowRewardFactor(ctx, coin.Denom) + if !foundBorrowFactor { + borrowFactor = sdk.ZeroDec() + } + + borrowRewardIndexes = append(borrowRewardIndexes, types.NewRewardIndex(coin.Denom, borrowFactor)) + } + + claim.BorrowRewardIndexes = borrowRewardIndexes + k.SetHardLiquidityProviderClaim(ctx, claim) +} + +// SynchronizeHardBorrowReward updates the claim object by adding any accumulated rewards +// and updating the reward index value +func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Borrow) { + claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower) + if !found { + return + } + + for _, coin := range borrow.Amount { + borrowFactor, found := k.GetHardBorrowRewardFactor(ctx, coin.Denom) + if !found { + continue + } + + borrowIndex, BorrowRewardIndex := claim.HasBorrowRewardIndex(coin.Denom) + if !BorrowRewardIndex { + continue + } + + userRewardFactor := claim.BorrowRewardIndexes[borrowIndex].RewardFactor + rewardsAccumulatedFactor := borrowFactor.Sub(userRewardFactor) + if rewardsAccumulatedFactor.IsZero() { + continue + } + claim.BorrowRewardIndexes[borrowIndex].RewardFactor = borrowFactor + + newRewardsAmount := rewardsAccumulatedFactor.Mul(borrow.Amount.AmountOf(coin.Denom).ToDec()).RoundInt() + if newRewardsAmount.IsZero() || newRewardsAmount.IsNegative() { + continue + } + + newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, newRewardsAmount) + claim.Reward = claim.Reward.Add(newRewardsCoin) + } + + k.SetHardLiquidityProviderClaim(ctx, claim) +} + +// UpdateHardSupplyIndexDenoms adds any new deposit denoms to the claim's supply reward index +func (k Keeper) UpdateHardSupplyIndexDenoms(ctx sdk.Context, deposit hardtypes.Deposit) { + claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor) + if !found { + claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, + sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()), + nil, nil, nil) + } + + supplyRewardIndexes := claim.SupplyRewardIndexes + for _, coin := range deposit.Amount { + _, hasIndex := claim.HasSupplyRewardIndex(coin.Denom) + if !hasIndex { + supplyFactor, foundSupplyFactor := k.GetHardSupplyRewardFactor(ctx, coin.Denom) + if foundSupplyFactor { + supplyRewardIndexes = append(supplyRewardIndexes, types.NewRewardIndex(coin.Denom, supplyFactor)) + } + } + } + if len(supplyRewardIndexes) == 0 { + return + } + claim.SupplyRewardIndexes = supplyRewardIndexes + k.SetHardLiquidityProviderClaim(ctx, claim) +} + +// UpdateHardBorrowIndexDenoms adds any new borrow denoms to the claim's supply reward index +func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Borrow) { + claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower) + if !found { + claim = types.NewHardLiquidityProviderClaim(borrow.Borrower, + sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()), + nil, nil, nil) + } + + borrowRewardIndexes := claim.BorrowRewardIndexes + for _, coin := range borrow.Amount { + _, hasIndex := claim.HasBorrowRewardIndex(coin.Denom) + if !hasIndex { + borrowFactor, foundBorrowFactor := k.GetHardBorrowRewardFactor(ctx, coin.Denom) + if foundBorrowFactor { + borrowRewardIndexes = append(borrowRewardIndexes, types.NewRewardIndex(coin.Denom, borrowFactor)) + } + } + } + if len(borrowRewardIndexes) == 0 { + return + } + claim.BorrowRewardIndexes = borrowRewardIndexes + k.SetHardLiquidityProviderClaim(ctx, claim) +} + // ZeroClaim zeroes out the claim object's rewards and returns the updated claim object func (k Keeper) ZeroClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim { claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt()) - k.SetClaim(ctx, claim) + k.SetUSDXMintingClaim(ctx, claim) return claim } @@ -149,8 +423,8 @@ func (k Keeper) SynchronizeClaim(ctx sdk.Context, claim types.USDXMintingClaim) // this function assumes a claim already exists, so don't call it if that's not the case func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim { - k.SynchronizeReward(ctx, cdp) - claim, _ := k.GetClaim(ctx, cdp.Owner) + k.SynchronizeUSDXMintingReward(ctx, cdp) + claim, _ := k.GetUSDXMintingClaim(ctx, cdp.Owner) return claim } diff --git a/x/incentive/keeper/rewards_test.go b/x/incentive/keeper/rewards_test.go index 2cad1154..477c161d 100644 --- a/x/incentive/keeper/rewards_test.go +++ b/x/incentive/keeper/rewards_test.go @@ -1,23 +1,21 @@ package keeper_test import ( - "fmt" - "testing" "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" tmtime "github.com/tendermint/tendermint/types/time" "github.com/kava-labs/kava/app" - cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper" cdptypes "github.com/kava-labs/kava/x/cdp/types" + "github.com/kava-labs/kava/x/hard" + hardtypes "github.com/kava-labs/kava/x/hard/types" "github.com/kava-labs/kava/x/incentive/types" ) -func (suite *KeeperTestSuite) TestAccumulateRewards() { +func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() { type args struct { ctype string rewardsPerSecond sdk.Coin @@ -67,7 +65,7 @@ func (suite *KeeperTestSuite) TestAccumulateRewards() { } for _, tc := range testCases { suite.Run(tc.name, func() { - suite.SetupWithCDPGenState() + suite.SetupWithGenState() suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) // setup cdp state @@ -76,28 +74,31 @@ func (suite *KeeperTestSuite) TestAccumulateRewards() { // setup incentive state params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, tc.args.initialTime.Add(time.Hour*24*365*5), ) suite.keeper.SetParams(suite.ctx, params) - suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) - suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) + suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) + suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) - rewardPeriod, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.args.ctype) + rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype) suite.Require().True(found) - err := suite.keeper.AccumulateRewards(suite.ctx, rewardPeriod) + err := suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod) suite.Require().NoError(err) - rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype) + rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype) suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) }) } } -func (suite *KeeperTestSuite) TestSyncRewards() { +func (suite *KeeperTestSuite) TestSynchronizeUSDXMintingReward() { type args struct { ctype string rewardsPerSecond sdk.Coin @@ -143,18 +144,21 @@ func (suite *KeeperTestSuite) TestSyncRewards() { } for _, tc := range testCases { suite.Run(tc.name, func() { - suite.SetupWithCDPGenState() + suite.SetupWithGenState() suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) // setup incentive state params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, tc.args.initialTime.Add(time.Hour*24*365*5), ) suite.keeper.SetParams(suite.ctx, params) - suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) - suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) + suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) + suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) // setup account state sk := suite.app.GetSupplyKeeper() @@ -166,7 +170,7 @@ func (suite *KeeperTestSuite) TestSyncRewards() { err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) suite.Require().NoError(err) - claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) + claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().True(found) suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) @@ -177,9 +181,9 @@ func (suite *KeeperTestSuite) TestSyncRewards() { updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) previousBlockTime = updatedBlockTime blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) - rewardPeriod, found := suite.keeper.GetRewardPeriod(blockCtx, tc.args.ctype) + rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype) suite.Require().True(found) - err := suite.keeper.AccumulateRewards(blockCtx, rewardPeriod) + err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod) suite.Require().NoError(err) } updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) @@ -187,137 +191,711 @@ func (suite *KeeperTestSuite) TestSyncRewards() { cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype) suite.Require().True(found) suite.Require().NotPanics(func() { - suite.keeper.SynchronizeReward(suite.ctx, cdp) + suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp) }) - rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype) + rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype) suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) - claim, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) - fmt.Println(claim) + claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) suite.Require().True(found) suite.Require().Equal(tc.args.expectedRewardFactor, claim.RewardIndexes[0].RewardFactor) suite.Require().Equal(tc.args.expectedRewards, claim.Reward) }) } - } -func TestRewardCalculation(t *testing.T) { +func (suite *KeeperTestSuite) TestAccumulateHardBorrowRewards() { + type args struct { + borrow sdk.Coin + rewardsPerSecond sdk.Coin + initialTime time.Time + timeElapsed int + expectedRewardFactor sdk.Dec + } + type test struct { + name string + args args + } + testCases := []test{ + { + "7 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardFactor: d("0.000000856478000001"), + }, + }, + { + "1 day", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardFactor: d("0.010571385600010177"), + }, + }, + { + "0 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardFactor: d("0.0"), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) - // Test Params - ctype := "bnb-a" - initialTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) - rewardsPerSecond := c("ukava", 122_354) - initialCollateral := c("bnb", 10_000_000_000) - initialPrincipal := c("usdx", 100_000_000) - oneYear := time.Hour * 24 * 365 - rewardPeriod := types.NewRewardPeriod( - true, - ctype, - initialTime, - initialTime.Add(4*oneYear), - rewardsPerSecond, - ) + // Mint coins to hard module account + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) - // Setup app and module params - _, addrs := app.GeneratePrivKeyAddressPairs(5) - tApp := app.NewTestApp() - ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: initialTime}) - tApp.InitializeFromGenesisStates( - app.NewAuthGenState(addrs[:1], []sdk.Coins{cs(initialCollateral)}), - NewPricefeedGenStateMulti(), - NewCDPGenStateHighInterest(), - NewIncentiveGenState(initialTime, initialTime.Add(oneYear), rewardPeriod), - ) + // setup incentive state + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime) + suite.keeper.SetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom, sdk.ZeroDec()) - // Create a CDP - cdpKeeper := tApp.GetCDPKeeper() - err := cdpKeeper.AddCdp( - ctx, - addrs[0], - initialCollateral, - initialPrincipal, - ctype, - ) - require.NoError(t, err) + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime) - // Calculate expected cdp reward using iteration + // User deposits and borrows to increase total borrowed amount + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(sdk.NewCoin(tc.args.borrow.Denom, tc.args.borrow.Amount.Mul(sdk.NewInt(2))))) + suite.Require().NoError(err) + err = hardKeeper.Borrow(suite.ctx, userAddr, sdk.NewCoins(tc.args.borrow)) + suite.Require().NoError(err) - // Use 10 blocks, each a very long 630720s, to total 6307200s or 1/5th of a year - // The cdp stability fee is set to the max value 500%, so this time ensures the debt increases a significant amount (doubles) - // High stability fees increase the chance of catching calculation bugs. - blockTimes := newRepeatingSliceInt(630720, 10) - expectedCDPReward := sdk.ZeroDec() //c(rewardPeriod.RewardsPerSecond.Denom, 0) - for _, bt := range blockTimes { - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Duration(int(time.Second) * bt))) + // Set up chain context at future time + runAtTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) + runCtx := suite.ctx.WithBlockTime(runAtTime) - // run cdp and incentive begin blockers to update factors - tApp.BeginBlocker(ctx, abci.RequestBeginBlock{}) + // Run Hard begin blocker in order to update the denom's index factor + hard.BeginBlocker(runCtx, suite.hardKeeper) - // calculate expected cdp reward - cdpBlockReward, err := calculateCDPBlockReward(ctx, cdpKeeper, addrs[0], ctype, sdk.NewInt(int64(bt)), rewardPeriod) - require.NoError(t, err) - expectedCDPReward = expectedCDPReward.Add(cdpBlockReward) + rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriod(runCtx, tc.args.borrow.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod) + suite.Require().NoError(err) + + rewardFactor, found := suite.keeper.GetHardBorrowRewardFactor(runCtx, tc.args.borrow.Denom) + suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) + }) + } +} + +func (suite *KeeperTestSuite) TestSynchronizeHardBorrowReward() { + type args struct { + borrow sdk.Coin + rewardsPerSecond sdk.Coin + initialTime time.Time + blockTimes []int + expectedRewardFactor sdk.Dec + expectedRewards sdk.Coin + } + type test struct { + name string + args args } - // calculate cdp reward using factor - cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, addrs[0], ctype) - require.True(t, found) - incentiveKeeper := tApp.GetIncentiveKeeper() - require.NotPanics(t, func() { - incentiveKeeper.SynchronizeReward(ctx, cdp) - }) - claim, found := incentiveKeeper.GetClaim(ctx, addrs[0]) - require.True(t, found) - - // Compare two methods of calculation - relativeError := expectedCDPReward.Sub(claim.Reward.Amount.ToDec()).Quo(expectedCDPReward).Abs() - maxError := d("0.0001") - require.Truef(t, relativeError.LT(maxError), - "percent diff %s > %s , expected: %s, actual %s,", relativeError, maxError, expectedCDPReward, claim.Reward.Amount, - ) -} - -// calculateCDPBlockReward computes the reward that should be distributed to a cdp for the current block. -func calculateCDPBlockReward(ctx sdk.Context, cdpKeeper cdpkeeper.Keeper, owner sdk.AccAddress, ctype string, timeElapsed sdk.Int, rewardPeriod types.RewardPeriod) (sdk.Dec, error) { - // Calculate total rewards to distribute this block - newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) - - // Calculate cdp's share of total debt - totalPrincipal := cdpKeeper.GetTotalPrincipal(ctx, ctype, types.PrincipalDenom).ToDec() - // cdpDebt - cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, owner, ctype) - if !found { - return sdk.Dec{}, fmt.Errorf("couldn't find cdp for owner '%s' and collateral type '%s'", owner, ctype) + testCases := []test{ + { + "10 blocks", + args{ + borrow: c("bnb", 10000000000), // TODO: 2 decimal diff from TestAccumulateHardBorrowRewards's borrow + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + expectedRewardFactor: d("0.001223540000173228"), + expectedRewards: c("hard", 12235400), + }, + }, + { + "10 blocks - long block time", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, + expectedRewardFactor: d("10.571385603126235340"), + expectedRewards: c("hard", 105713856031), + }, + }, } - accumulatedInterest := cdpKeeper.CalculateNewInterest(ctx, cdp) - cdpDebt := cdp.Principal.Add(cdp.AccumulatedFees).Add(accumulatedInterest).Amount + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) - // Calculate cdp's reward - return newRewards.Mul(cdpDebt).ToDec().Quo(totalPrincipal), nil + // Mint coins to hard module account + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) + + // setup incentive state + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime) + suite.keeper.SetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom, sdk.ZeroDec()) + + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime) + + // User deposits and borrows to increase total borrowed amount + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(sdk.NewCoin(tc.args.borrow.Denom, tc.args.borrow.Amount.Mul(sdk.NewInt(2))))) + suite.Require().NoError(err) + err = hardKeeper.Borrow(suite.ctx, userAddr, sdk.NewCoins(tc.args.borrow)) + suite.Require().NoError(err) + + // Check that Hard hooks initialized a HardLiquidityProviderClaim + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), claim.BorrowRewardIndexes[0].RewardFactor) + + // Run accumulator at several intervals + var timeElapsed int + previousBlockTime := suite.ctx.BlockTime() + for _, t := range tc.args.blockTimes { + timeElapsed += t + updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) + previousBlockTime = updatedBlockTime + blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) + + // Run Hard begin blocker for each block ctx to update denom's interest factor + hard.BeginBlocker(blockCtx, suite.hardKeeper) + + rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriod(blockCtx, tc.args.borrow.Denom) + suite.Require().True(found) + + err := suite.keeper.AccumulateHardBorrowRewards(blockCtx, rewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + // After we've accumulated, run synchronize + borrow, found := hardKeeper.GetBorrow(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().NotPanics(func() { + suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow) + }) + + // Check that reward factor and claim have been updated as expected + rewardFactor, found := suite.keeper.GetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom) + suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) + + claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().Equal(tc.args.expectedRewardFactor, claim.BorrowRewardIndexes[0].RewardFactor) + suite.Require().Equal(tc.args.expectedRewards, claim.Reward) + }) + } } -func (suite *KeeperTestSuite) SetupWithCDPGenState() { +func (suite *KeeperTestSuite) TestAccumulateHardSupplyRewards() { + type args struct { + deposit sdk.Coin + rewardsPerSecond sdk.Coin + initialTime time.Time + timeElapsed int + expectedRewardFactor sdk.Dec + } + type test struct { + name string + args args + } + testCases := []test{ + { + "7 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardFactor: d("0.000000856478000000"), + }, + }, + { + "1 day", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardFactor: d("0.010571385600000000"), + }, + }, + { + "0 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardFactor: d("0.0"), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + + // Mint coins to hard module account + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) + + // Set up incentive state + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime) + suite.keeper.SetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom, sdk.ZeroDec()) + + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime) + + // User deposits to increase total supplied amount + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(tc.args.deposit)) + suite.Require().NoError(err) + + // Set up chain context at future time + runAtTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) + runCtx := suite.ctx.WithBlockTime(runAtTime) + + // Run Hard begin blocker in order to update the denom's index factor + hard.BeginBlocker(runCtx, suite.hardKeeper) + + rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriod(runCtx, tc.args.deposit.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod) + suite.Require().NoError(err) + + rewardFactor, found := suite.keeper.GetHardSupplyRewardFactor(runCtx, tc.args.deposit.Denom) + suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) + }) + } +} + +func (suite *KeeperTestSuite) TestSynchronizeHardSupplyReward() { + type args struct { + deposit sdk.Coin + rewardsPerSecond sdk.Coin + initialTime time.Time + blockTimes []int + expectedRewardFactor sdk.Dec + expectedRewards sdk.Coin + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "10 blocks", + args{ + deposit: c("bnb", 10000000000), // TODO: 2 decimal diff + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + expectedRewardFactor: d("0.001223540000000000"), + expectedRewards: c("hard", 12235400), + }, + }, + { + "10 blocks - long block time", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, + expectedRewardFactor: d("10.571385600000000000"), + expectedRewards: c("hard", 105713856000), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + + // Mint coins to hard module account + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) + + // setup incentive state + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime) + suite.keeper.SetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom, sdk.ZeroDec()) + + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0")) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime) + + // User deposits and borrows to increase total borrowed amount + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(tc.args.deposit)) + suite.Require().NoError(err) + + // Check that Hard hooks initialized a HardLiquidityProviderClaim + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), claim.SupplyRewardIndexes[0].RewardFactor) + + // Run accumulator at several intervals + var timeElapsed int + previousBlockTime := suite.ctx.BlockTime() + for _, t := range tc.args.blockTimes { + timeElapsed += t + updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) + previousBlockTime = updatedBlockTime + blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) + + // Run Hard begin blocker for each block ctx to update denom's interest factor + hard.BeginBlocker(blockCtx, suite.hardKeeper) + + rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriod(blockCtx, tc.args.deposit.Denom) + suite.Require().True(found) + + err := suite.keeper.AccumulateHardSupplyRewards(blockCtx, rewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + // After we've accumulated, run synchronize + deposit, found := hardKeeper.GetDeposit(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().NotPanics(func() { + suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit) + }) + + // Check that reward factor and claim have been updated as expected + rewardFactor, found := suite.keeper.GetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom) + suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) + + claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().Equal(tc.args.expectedRewardFactor, claim.SupplyRewardIndexes[0].RewardFactor) + suite.Require().Equal(tc.args.expectedRewards, claim.Reward) + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateHardSupplyIndexDenoms() { + type args struct { + firstDeposit sdk.Coins + secondDeposit sdk.Coins + rewardsPerSecond sdk.Coin + initialTime time.Time + expectedSupplyIndexDenoms []string + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "update adds one supply reward index", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "update adds multiple supply reward indexes", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000), c("btcb", 10000000000), c("xrp", 10000000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava", "btcb", "xrp"}, + }, + }, + { + "update doesn't add duplicate supply reward index for same denom", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("bnb", 5000000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb"}, + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + + // Mint coins to hard module account + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000))) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) + + // Set up generic reward periods + var rewardPeriods types.RewardPeriods + for _, denom := range tc.args.expectedSupplyIndexDenoms { + rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) + rewardPeriods = append(rewardPeriods, rewardPeriod) + } + + // Setup incentive state + params := types.NewParams( + rewardPeriods, rewardPeriods, rewardPeriods, rewardPeriods, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + + // Set each denom's previous accrual time and supply reward factor + for _, denom := range tc.args.expectedSupplyIndexDenoms { + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, tc.args.initialTime) + suite.keeper.SetHardSupplyRewardFactor(suite.ctx, denom, sdk.ZeroDec()) + } + + // User deposits (first time) + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, tc.args.firstDeposit) + suite.Require().NoError(err) + + // Confirm that a claim was created and populated with the correct supply indexes + claimAfterFirstDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, coin := range tc.args.firstDeposit { + _, hasIndex := claimAfterFirstDeposit.HasSupplyRewardIndex(coin.Denom) + suite.Require().True(hasIndex) + } + suite.Require().True(len(claimAfterFirstDeposit.SupplyRewardIndexes) == len(tc.args.firstDeposit)) + + // User deposits (second time) + err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.secondDeposit) + suite.Require().NoError(err) + + // Confirm that the claim contains all expected supply indexes + claimAfterSecondDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, denom := range tc.args.expectedSupplyIndexDenoms { + _, hasIndex := claimAfterSecondDeposit.HasSupplyRewardIndex(denom) + suite.Require().True(hasIndex) + } + suite.Require().True(len(claimAfterSecondDeposit.SupplyRewardIndexes) == len(tc.args.expectedSupplyIndexDenoms)) + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateHardBorrowIndexDenoms() { + type args struct { + initialDeposit sdk.Coins + firstBorrow sdk.Coins + secondBorrow sdk.Coins + rewardsPerSecond sdk.Coin + initialTime time.Time + expectedBorrowIndexDenoms []string + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "update adds one borrow reward index", + args{ + initialDeposit: cs(c("bnb", 10000000000)), + firstBorrow: cs(c("bnb", 50000000)), + secondBorrow: cs(c("ukava", 500000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "update adds multiple borrow supply reward indexes", + args{ + initialDeposit: cs(c("btcb", 10000000000)), + firstBorrow: cs(c("btcb", 50000000)), + secondBorrow: cs(c("ukava", 500000000), c("bnb", 50000000000), c("xrp", 50000000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"btcb", "ukava", "bnb", "xrp"}, + }, + }, + { + "update doesn't add duplicate borrow reward index for same denom", + args{ + initialDeposit: cs(c("bnb", 100000000000)), + firstBorrow: cs(c("bnb", 50000000)), + secondBorrow: cs(c("bnb", 50000000000)), + rewardsPerSecond: c("hard", 122354), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"bnb"}, + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + + // Mint coins to hard module account so it can service borrow requests + supplyKeeper := suite.app.GetSupplyKeeper() + hardMaccCoins := tc.args.firstBorrow.Add(tc.args.secondBorrow...) + supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins) + + // Set up generic reward periods + var rewardPeriods types.RewardPeriods + for _, denom := range tc.args.expectedBorrowIndexDenoms { + rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) + rewardPeriods = append(rewardPeriods, rewardPeriod) + } + + // Setup incentive state + params := types.NewParams( + rewardPeriods, rewardPeriods, rewardPeriods, rewardPeriods, + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + tc.args.initialTime.Add(time.Hour*24*365*5), + ) + suite.keeper.SetParams(suite.ctx, params) + // Set each initial deposit denom's previous accrual time and supply reward factor + for _, coin := range tc.args.initialDeposit { + suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) + suite.keeper.SetHardBorrowRewardFactor(suite.ctx, coin.Denom, sdk.ZeroDec()) + } + + // Set each expected borrow denom's previous accrual time and borrow reward factor + for _, denom := range tc.args.expectedBorrowIndexDenoms { + suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, tc.args.initialTime) + suite.keeper.SetHardBorrowRewardFactor(suite.ctx, denom, sdk.ZeroDec()) + } + + // User deposits initial funds (so that user can borrow) + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] + err := hardKeeper.Deposit(suite.ctx, userAddr, tc.args.initialDeposit) + suite.Require().NoError(err) + + // Confirm that claim exists but no borrow reward indexes have been added + claimAfterDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.Require().Equal(0, len(claimAfterDeposit.BorrowRewardIndexes)) + + // User borrows (first time) + err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.firstBorrow) + suite.Require().NoError(err) + + // Confirm that claim's borrow reward indexes have been updated + claimAfterFirstBorrow, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, coin := range tc.args.firstBorrow { + _, hasIndex := claimAfterFirstBorrow.HasBorrowRewardIndex(coin.Denom) + suite.Require().True(hasIndex) + } + suite.Require().True(len(claimAfterFirstBorrow.BorrowRewardIndexes) == len(tc.args.firstBorrow)) + + // User borrows (second time) + err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.secondBorrow) + suite.Require().NoError(err) + + // Confirm that claim's borrow reward indexes contain expected values + claimAfterSecondBorrow, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, coin := range tc.args.secondBorrow { + _, hasIndex := claimAfterSecondBorrow.HasBorrowRewardIndex(coin.Denom) + suite.Require().True(hasIndex) + } + suite.Require().True(len(claimAfterSecondBorrow.BorrowRewardIndexes) == len(tc.args.expectedBorrowIndexDenoms)) + }) + } +} + +func (suite *KeeperTestSuite) SetupWithGenState() { tApp := app.NewTestApp() ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + + _, addrs := app.GeneratePrivKeyAddressPairs(5) + + authGS := app.NewAuthGenState( + []sdk.AccAddress{addrs[3]}, + []sdk.Coins{ + sdk.NewCoins( + sdk.NewCoin("bnb", sdk.NewInt(1000000000000000)), + sdk.NewCoin("ukava", sdk.NewInt(1000000000000000)), + sdk.NewCoin("btcb", sdk.NewInt(1000000000000000)), + sdk.NewCoin("xrp", sdk.NewInt(1000000000000000)), + ), + }, + ) + tApp.InitializeFromGenesisStates( + authGS, NewPricefeedGenStateMulti(), NewCDPGenStateMulti(), + NewHardGenStateMulti(), ) - _, addrs := app.GeneratePrivKeyAddressPairs(5) + keeper := tApp.GetIncentiveKeeper() + hardKeeper := tApp.GetHardKeeper() suite.app = tApp suite.ctx = ctx suite.keeper = keeper + suite.hardKeeper = hardKeeper suite.addrs = addrs } - -// newRepeatingSliceInt creates a slice of the specified length containing a single repeating element. -func newRepeatingSliceInt(element int, length int) []int { - slice := make([]int, length) - for i := 0; i < length; i++ { - slice[i] = element - } - return slice -} diff --git a/x/incentive/simulation/decoder.go b/x/incentive/simulation/decoder.go index 89665073..03da4f2f 100644 --- a/x/incentive/simulation/decoder.go +++ b/x/incentive/simulation/decoder.go @@ -17,24 +17,66 @@ import ( func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { - case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix): + case bytes.Equal(kvA.Key[:1], types.USDXMintingClaimKeyPrefix): var claimA, claimB types.USDXMintingClaim cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA) cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB) return fmt.Sprintf("%v\n%v", claimA, claimB) - case bytes.Equal(kvA.Key[:1], types.BlockTimeKey): + case bytes.Equal(kvA.Key[:1], types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix): var timeA, timeB time.Time cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) return fmt.Sprintf("%s\n%s", timeA, timeB) - case bytes.Equal(kvA.Key[:1], types.RewardFactorKey): + case bytes.Equal(kvA.Key[:1], types.USDXMintingRewardFactorKeyPrefix): var factorA, factorB sdk.Dec cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA) cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB) return fmt.Sprintf("%s\n%s", factorA, factorB) + // case bytes.Equal(kvA.Key[:1], types.HardLiquidityClaimKeyPrefix): + // var claimA, claimB types.HardLiquidityProviderClaim + // cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB) + // return fmt.Sprintf("%v\n%v", claimA, claimB) + + // case bytes.Equal(kvA.Key[:1], types.PreviousHardSupplyRewardAccrualTimeKeyPrefix): + // var timeA, timeB time.Time + // cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) + // return fmt.Sprintf("%s\n%s", timeA, timeB) + + // case bytes.Equal(kvA.Key[:1], types.HardSupplyRewardFactorKeyPrefix): + // var factorA, factorB sdk.Dec + // cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB) + // return fmt.Sprintf("%s\n%s", factorA, factorB) + + // case bytes.Equal(kvA.Key[:1], types.PreviousHardBorrowRewardAccrualTimeKeyPrefix): + // var timeA, timeB time.Time + // cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) + // return fmt.Sprintf("%s\n%s", timeA, timeB) + + // case bytes.Equal(kvA.Key[:1], types.HardSupplyRewardFactorKeyPrefix): + // var factorA, factorB sdk.Dec + // cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB) + // return fmt.Sprintf("%s\n%s", factorA, factorB) + + // case bytes.Equal(kvA.Key[:1], types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix): + // var timeA, timeB time.Time + // cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) + // return fmt.Sprintf("%s\n%s", timeA, timeB) + + // case bytes.Equal(kvA.Key[:1], types.HardDelegatorRewardFactorKeyPrefix): + // var factorA, factorB sdk.Dec + // cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA) + // cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB) + // return fmt.Sprintf("%s\n%s", factorA, factorB) + default: panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) } diff --git a/x/incentive/simulation/decoder_test.go b/x/incentive/simulation/decoder_test.go index 7ce646bf..8bf5daa0 100644 --- a/x/incentive/simulation/decoder_test.go +++ b/x/incentive/simulation/decoder_test.go @@ -30,9 +30,16 @@ func TestDecodeDistributionStore(t *testing.T) { factor := sdk.ZeroDec() kvPairs := kv.Pairs{ - kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)}, - kv.Pair{Key: []byte(types.BlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, - kv.Pair{Key: []byte(types.RewardFactorKey), Value: cdc.MustMarshalBinaryBare(factor)}, + kv.Pair{Key: types.USDXMintingClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)}, + kv.Pair{Key: []byte(types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, + kv.Pair{Key: []byte(types.USDXMintingRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)}, + // kv.Pair{Key: types.HardLiquidityClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)}, + // kv.Pair{Key: []byte(types.HardSupplyRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)}, + // kv.Pair{Key: []byte(types.PreviousHardSupplyRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, + // kv.Pair{Key: []byte(types.HardBorrowRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)}, + // kv.Pair{Key: []byte(types.PreviousHardBorrowRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, + // kv.Pair{Key: []byte(types.HardDelegatorRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)}, + // kv.Pair{Key: []byte(types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } @@ -40,9 +47,16 @@ func TestDecodeDistributionStore(t *testing.T) { name string expectedLog string }{ - {"Claim", fmt.Sprintf("%v\n%v", claim, claim)}, - {"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, - {"RewardFactor", fmt.Sprintf("%v\n%v", factor, factor)}, + {"USDXMintingClaim", fmt.Sprintf("%v\n%v", claim, claim)}, + {"PreviousUSDXMintingRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + {"USDXMintingRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)}, + // {"HardLiquidityClaim", fmt.Sprintf("%v\n%v", claim, claim)}, + // {"PreviousHardSupplyRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + // {"HardSupplyRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)}, + // {"PreviousHardBorrowRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + // {"HardBorrowRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)}, + // {"PreviousHardDelegatorRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + // {"HardSupplyDelegatorFactor", fmt.Sprintf("%v\n%v", factor, factor)}, {"other", ""}, } for i, tt := range tests { diff --git a/x/incentive/types/claims.go b/x/incentive/types/claims.go index 3ff9e140..8d5d0db0 100644 --- a/x/incentive/types/claims.go +++ b/x/incentive/types/claims.go @@ -8,40 +8,91 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// USDXMintingClaim stores the usdx mintng rewards that can be claimed by owner -type USDXMintingClaim struct { - Owner sdk.AccAddress `json:"owner" yaml:"owner"` - Reward sdk.Coin `json:"reward" yaml:"reward"` - RewardIndexes RewardIndexes `json:"reward_indexes" yaml:"reward_indexes"` +const ( + USDXMintingClaimType = "usdx_minting" + HardLiquidityProviderClaimType = "hard_liquidity_provider" +) + +// Claim is an interface for handling common claim actions +type Claim interface { + GetOwner() sdk.AccAddress + GetReward() sdk.Coin + GetType() string } -// NewUSDXMintingClaim returns a new USDXMintingClaim -func NewUSDXMintingClaim(owner sdk.AccAddress, reward sdk.Coin, rewardIndexes RewardIndexes) USDXMintingClaim { - return USDXMintingClaim{ - Owner: owner, - Reward: reward, - RewardIndexes: rewardIndexes, - } +// Claims is a slice of Claim +type Claims []Claim + +// BaseClaim is a common type shared by all Claims +type BaseClaim struct { + Owner sdk.AccAddress `json:"owner" yaml:"owner"` + Reward sdk.Coin `json:"reward" yaml:"reward"` } -// Validate performs a basic check of a Claim fields. -func (c USDXMintingClaim) Validate() error { +// GetOwner is a getter for Claim Owner +func (c BaseClaim) GetOwner() sdk.AccAddress { return c.Owner } + +// GetReward is a getter for Claim Reward +func (c BaseClaim) GetReward() sdk.Coin { return c.Reward } + +// GetType returns the claim type, used to identify auctions in event attributes +func (c BaseClaim) GetType() string { return "base" } + +// Validate performs a basic check of a BaseClaim fields +func (c BaseClaim) Validate() error { if c.Owner.Empty() { return errors.New("claim owner cannot be empty") } if !c.Reward.IsValid() { return fmt.Errorf("invalid reward amount: %s", c.Reward) } - return c.RewardIndexes.Validate() + return nil +} + +// String implements fmt.Stringer +func (c BaseClaim) String() string { + return fmt.Sprintf(`Claim: + Owner: %s, + Reward: %s, + `, c.Owner, c.Reward) +} + +// -------------- Custom Claim Types -------------- + +// USDXMintingClaim is for USDX minting rewards +type USDXMintingClaim struct { + BaseClaim `json:"base_claim" yaml:"base_claim"` + RewardIndexes RewardIndexes `json:"reward_indexes" yaml:"reward_indexes"` +} + +// NewUSDXMintingClaim returns a new USDXMintingClaim +func NewUSDXMintingClaim(owner sdk.AccAddress, reward sdk.Coin, rewardIndexes RewardIndexes) USDXMintingClaim { + return USDXMintingClaim{ + BaseClaim: BaseClaim{ + Owner: owner, + Reward: reward, + }, + RewardIndexes: rewardIndexes, + } +} + +// GetType returns the claim type, used to identify auctions in event attributes +func (c USDXMintingClaim) GetType() string { return USDXMintingClaimType } + +// Validate performs a basic check of a Claim fields +func (c USDXMintingClaim) Validate() error { + if err := c.RewardIndexes.Validate(); err != nil { + return err + } + + return c.BaseClaim.Validate() } // String implements fmt.Stringer func (c USDXMintingClaim) String() string { - return fmt.Sprintf(`Claim: - Owner: %s, - Reward: %s, + return fmt.Sprintf(`%s Reward Indexes: %s, - `, c.Owner, c.Reward, c.RewardIndexes) + `, c.BaseClaim, c.RewardIndexes) } // HasRewardIndex check if a claim has a reward index for the input collateral type @@ -54,7 +105,7 @@ func (c USDXMintingClaim) HasRewardIndex(collateralType string) (int64, bool) { return 0, false } -// USDXMintingClaims array of USDXMintingClaim +// USDXMintingClaims slice of USDXMintingClaim type USDXMintingClaims []USDXMintingClaim // Validate checks if all the claims are valid and there are no duplicated @@ -69,6 +120,106 @@ func (cs USDXMintingClaims) Validate() error { return nil } +// HardLiquidityProviderClaim stores the hard liquidity provider rewards that can be claimed by owner +type HardLiquidityProviderClaim struct { + BaseClaim `json:"base_claim" yaml:"base_claim"` + SupplyRewardIndexes RewardIndexes `json:"supply_reward_indexes" yaml:"supply_reward_indexes"` + BorrowRewardIndexes RewardIndexes `json:"borrow_reward_indexes" yaml:"borrow_reward_indexes"` + DelegationRewardIndexes RewardIndexes `json:"delegation_reward_indexes" yaml:"delegation_reward_indexes"` +} + +// NewHardLiquidityProviderClaim returns a new HardLiquidityProviderClaim +func NewHardLiquidityProviderClaim(owner sdk.AccAddress, reward sdk.Coin, supplyRewardIndexes, + borrowRewardIndexes, delegationRewardIndexes RewardIndexes) HardLiquidityProviderClaim { + return HardLiquidityProviderClaim{ + BaseClaim: BaseClaim{ + Owner: owner, + Reward: reward, + }, + SupplyRewardIndexes: supplyRewardIndexes, + BorrowRewardIndexes: borrowRewardIndexes, + DelegationRewardIndexes: delegationRewardIndexes, + } +} + +// GetType returns the claim type, used to identify auctions in event attributes +func (c HardLiquidityProviderClaim) GetType() string { return HardLiquidityProviderClaimType } + +// Validate performs a basic check of a HardLiquidityProviderClaim fields +func (c HardLiquidityProviderClaim) Validate() error { + if err := c.SupplyRewardIndexes.Validate(); err != nil { + return err + } + + if err := c.BorrowRewardIndexes.Validate(); err != nil { + return err + } + + if err := c.DelegationRewardIndexes.Validate(); err != nil { + return err + } + + return c.BaseClaim.Validate() +} + +// String implements fmt.Stringer +func (c HardLiquidityProviderClaim) String() string { + return fmt.Sprintf(`%s + Supply Reward Indexes: %s, + Borrow Reward Indexes: %s, + Delegation Reward Indexes: %s, + `, c.BaseClaim, c.SupplyRewardIndexes, c.BorrowRewardIndexes, c.DelegationRewardIndexes) +} + +// HasSupplyRewardIndex check if a claim has a supply reward index for the input collateral type +func (c HardLiquidityProviderClaim) HasSupplyRewardIndex(denom string) (int64, bool) { + for index, ri := range c.SupplyRewardIndexes { + if ri.CollateralType == denom { + return int64(index), true + } + } + return 0, false +} + +// HasBorrowRewardIndex check if a claim has a borrow reward index for the input collateral type +func (c HardLiquidityProviderClaim) HasBorrowRewardIndex(denom string) (int64, bool) { + for index, ri := range c.BorrowRewardIndexes { + if ri.CollateralType == denom { + return int64(index), true + } + } + return 0, false +} + +// HasDelegationRewardIndex check if a claim has a delegation reward index for the input collateral type +func (c HardLiquidityProviderClaim) HasDelegationRewardIndex(collateralType string) (int64, bool) { + for index, ri := range c.SupplyRewardIndexes { + if ri.CollateralType == collateralType { + return int64(index), true + } + } + return 0, false +} + +// HardLiquidityProviderClaims slice of HardLiquidityProviderClaim +type HardLiquidityProviderClaims []HardLiquidityProviderClaim + +// Validate checks if all the claims are valid and there are no duplicated +// entries. +func (cs HardLiquidityProviderClaims) Validate() error { + for _, c := range cs { + if err := c.Validate(); err != nil { + return err + } + } + + return nil +} + +// -------------- Subcomponents of Custom Claim Types -------------- + +// TODO: refactor RewardPeriod name from 'collateralType' to 'denom' + // RewardIndex stores reward accumulation information type RewardIndex struct { CollateralType string `json:"collateral_type" yaml:"collateral_type"` diff --git a/x/incentive/types/claims_test.go b/x/incentive/types/claims_test.go index 5d6cd9ab..3cbf4703 100644 --- a/x/incentive/types/claims_test.go +++ b/x/incentive/types/claims_test.go @@ -28,7 +28,11 @@ func TestClaimsValidate(t *testing.T) { { "invalid owner", USDXMintingClaims{ - {Owner: nil}, + USDXMintingClaim{ + BaseClaim: BaseClaim{ + Owner: nil, + }, + }, }, false, }, @@ -36,8 +40,10 @@ func TestClaimsValidate(t *testing.T) { "invalid reward", USDXMintingClaims{ { - Owner: owner, - Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()}, + BaseClaim: BaseClaim{ + Owner: owner, + Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()}, + }, }, }, false, @@ -46,8 +52,10 @@ func TestClaimsValidate(t *testing.T) { "invalid collateral type", USDXMintingClaims{ { - Owner: owner, - Reward: sdk.NewCoin("bnb", sdk.OneInt()), + BaseClaim: BaseClaim{ + Owner: owner, + Reward: sdk.NewCoin("bnb", sdk.OneInt()), + }, RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}}, }, }, diff --git a/x/incentive/types/codec.go b/x/incentive/types/codec.go index 262197e4..8758ee8e 100644 --- a/x/incentive/types/codec.go +++ b/x/incentive/types/codec.go @@ -14,5 +14,10 @@ func init() { // RegisterCodec registers the necessary types for incentive module func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*Claim)(nil), nil) + cdc.RegisterConcrete(USDXMintingClaim{}, "incentive/USDXMintingClaim", nil) + cdc.RegisterConcrete(HardLiquidityProviderClaim{}, "incentive/HardLiquidityProviderClaim", nil) + + // Register msgs cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil) } diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index e4e8e8a0..55a8545b 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -6,23 +6,31 @@ import ( supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" cdptypes "github.com/kava-labs/kava/x/cdp/types" + hardtypes "github.com/kava-labs/kava/x/hard/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 { + GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool) GetTotalPrincipal(ctx sdk.Context, collateralType string, principalDenom string) (total sdk.Int) GetCdpByOwnerAndCollateralType(ctx sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool) - GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool) GetCollateral(ctx sdk.Context, collateralType string) (cdptypes.CollateralParam, bool) } +// HardKeeper defines the expected hard keeper for interacting with Hard protocol +type HardKeeper interface { + GetSupplyInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) + GetBorrowInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) + GetBorrowedCoins(ctx sdk.Context) (coins sdk.Coins, found bool) + GetSuppliedCoins(ctx sdk.Context) (coins sdk.Coins, found bool) +} + // AccountKeeper defines the expected keeper interface for interacting with account type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account @@ -34,3 +42,13 @@ type CDPHooks interface { AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) } + +// HARDHooks event hooks for other keepers to run code in response to HARD modifications +type HARDHooks interface { + AfterDepositCreated(ctx sdk.Context, deposit hardtypes.Deposit) + BeforeDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) + AfterDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) + AfterBorrowCreated(ctx sdk.Context, borrow hardtypes.Borrow) + BeforeBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) + AfterBorrowModified(ctx sdk.Context, deposit hardtypes.Deposit) +} diff --git a/x/incentive/types/genesis.go b/x/incentive/types/genesis.go index b64051d7..ee813812 100644 --- a/x/incentive/types/genesis.go +++ b/x/incentive/types/genesis.go @@ -89,9 +89,6 @@ func (gats GenesisAccumulationTimes) Validate() error { // Validate performs validation of GenesisAccumulationTime func (gat GenesisAccumulationTime) Validate() error { - if gat.RewardFactor == (sdk.Dec{}) { - return fmt.Errorf("reward factor not initialized for %s", gat.CollateralType) - } if gat.RewardFactor.LT(sdk.ZeroDec()) { return fmt.Errorf("reward factor should be ≥ 0.0, is %s for %s", gat.RewardFactor, gat.CollateralType) } diff --git a/x/incentive/types/genesis_test.go b/x/incentive/types/genesis_test.go index e9bda436..76a2226f 100644 --- a/x/incentive/types/genesis_test.go +++ b/x/incentive/types/genesis_test.go @@ -5,11 +5,9 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" ) func TestGenesisStateValidate(t *testing.T) { @@ -44,7 +42,24 @@ func TestGenesisStateValidate(t *testing.T) { { name: "valid", args: args{ - params: NewParams(RewardPeriods{NewRewardPeriod(true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), sdk.NewCoin("ukava", sdk.NewInt(25000)))}, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC)), + params: NewParams( + RewardPeriods{ + NewRewardPeriod( + true, + "bnb-a", + time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), + time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), + sdk.NewCoin("ukava", sdk.NewInt(25000)), + ), + }, + DefaultRewardPeriods, + DefaultRewardPeriods, + DefaultRewardPeriods, + Multipliers{ + NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33")), + }, + time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), + ), genAccTimes: GenesisAccumulationTimes{GenesisAccumulationTime{ CollateralType: "bnb-a", PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), @@ -52,9 +67,10 @@ func TestGenesisStateValidate(t *testing.T) { }}, claims: USDXMintingClaims{ { - Owner: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))), - Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), - + BaseClaim: BaseClaim{ + Owner: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))), + Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), + }, RewardIndexes: []RewardIndex{ { CollateralType: "bnb-a", @@ -86,24 +102,6 @@ func TestGenesisStateValidate(t *testing.T) { contains: "reward factor should be ≥ 0.0", }, }, - { - name: "invalid genesis accumulation time", - args: args{ - params: DefaultParams(), - genAccTimes: GenesisAccumulationTimes{ - { - CollateralType: "btcb-a", - PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), - RewardFactor: sdk.Dec{}, - }, - }, - claims: DefaultClaims, - }, - errArgs: errArgs{ - expectPass: false, - contains: "reward factor not initialized", - }, - }, { name: "invalid claim", args: args{ @@ -111,9 +109,10 @@ func TestGenesisStateValidate(t *testing.T) { genAccTimes: DefaultGenesisAccumulationTimes, claims: USDXMintingClaims{ { - Owner: sdk.AccAddress{}, - Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), - + BaseClaim: BaseClaim{ + Owner: sdk.AccAddress{}, + Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), + }, RewardIndexes: []RewardIndex{ { CollateralType: "bnb-a", diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go index a930886a..e9bfb43c 100644 --- a/x/incentive/types/keys.go +++ b/x/incentive/types/keys.go @@ -17,11 +17,23 @@ const ( QuerierRoute = ModuleName ) +// TODO: Refactor so that each incentive type has: +// 1. [Incentive]ClaimKeyPrefix +// 2. [Incentve]AccumulatorKeyPrefix { PreviousAccrualTime block.Time, IndexFactors types.IndexFactors } + // Key Prefixes var ( - ClaimKeyPrefix = []byte{0x01} // prefix for keys that store claims - BlockTimeKey = []byte{0x02} // prefix for key that stores the blocktime - RewardFactorKey = []byte{0x03} // prefix for key that stores reward factors + USDXMintingClaimKeyPrefix = []byte{0x01} // prefix for keys that store USDX minting claims + USDXMintingRewardFactorKeyPrefix = []byte{0x02} // prefix for key that stores USDX minting reward factors + PreviousUSDXMintingRewardAccrualTimeKeyPrefix = []byte{0x03} // prefix for key that stores the blocktime + HardLiquidityClaimKeyPrefix = []byte{0x04} // prefix for keys that store Hard liquidity claims + HardSupplyRewardFactorKeyPrefix = []byte{0x05} // prefix for key that stores Hard supply reward factors + PreviousHardSupplyRewardAccrualTimeKeyPrefix = []byte{0x06} // prefix for key that stores the previous time Hard supply rewards accrued + HardBorrowRewardFactorKeyPrefix = []byte{0x07} // prefix for key that stores Hard borrow reward factors + PreviousHardBorrowRewardAccrualTimeKeyPrefix = []byte{0x08} // prefix for key that stores the previous time Hard borrow rewards accrued + HardDelegatorRewardFactorKeyPrefix = []byte{0x09} // prefix for key that stores Hard delegator reward factors + PreviousHardDelegatorRewardAccrualTimeKeyPrefix = []byte{0x10} // prefix for key that stores the previous time Hard delegator rewards accrued - USDXMintingRewardDenom = "ukava" + USDXMintingRewardDenom = "ukava" + HardLiquidityRewardDenom = "hard" ) diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index b79531b1..cfb4b99d 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -24,8 +24,10 @@ const ( // Parameter keys and default values var ( - KeyActive = []byte("Active") - KeyRewards = []byte("RewardPeriods") + KeyUSDXMintingRewardPeriods = []byte("USDXMintingRewardPeriods") + KeyHardSupplyRewardPeriods = []byte("HardSupplyRewardPeriods") + KeyHardBorrowRewardPeriods = []byte("HardBorrowRewardPeriods") + KeyHardDelegatorRewardPeriods = []byte("HardDelegatorRewardPeriods") KeyClaimEnd = []byte("ClaimEnd") KeyMultipliers = []byte("ClaimMultipliers") DefaultActive = false @@ -41,32 +43,44 @@ var ( // Params governance parameters for the incentive module type Params struct { - RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"` - ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` - ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` + USDXMintingRewardPeriods RewardPeriods `json:"usdx_minting_reward_periods" yaml:"usdx_minting_reward_periods"` + HardSupplyRewardPeriods RewardPeriods `json:"hard_supply_reward_periods" yaml:"hard_supply_reward_periods"` + HardBorrowRewardPeriods RewardPeriods `json:"hard_borrow_reward_periods" yaml:"hard_borrow_reward_periods"` + HardDelegatorRewardPeriods RewardPeriods `json:"hard_delegator_reward_periods" yaml:"hard_delegator_reward_periods"` + ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` + ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` } // NewParams returns a new params object -func NewParams(rewards RewardPeriods, multipliers Multipliers, claimEnd time.Time) Params { +func NewParams(usdxMinting, hardSupply, hardBorrow, hardDelegator RewardPeriods, + multipliers Multipliers, claimEnd time.Time) Params { return Params{ - RewardPeriods: rewards, - ClaimMultipliers: multipliers, - ClaimEnd: claimEnd, + USDXMintingRewardPeriods: usdxMinting, + HardSupplyRewardPeriods: hardSupply, + HardBorrowRewardPeriods: hardBorrow, + HardDelegatorRewardPeriods: hardDelegator, + ClaimMultipliers: multipliers, + ClaimEnd: claimEnd, } } // DefaultParams returns default params for incentive module func DefaultParams() Params { - return NewParams(DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd) + return NewParams(DefaultRewardPeriods, DefaultRewardPeriods, + DefaultRewardPeriods, DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd) } // String implements fmt.Stringer func (p Params) String() string { return fmt.Sprintf(`Params: - Rewards: %s + USDX Minting Reward Periods: %s + Hard Supply Reward Periods: %s + Hard Borrow Reward Periods: %s + Hard Delegator Reward Periods: %s Claim Multipliers :%s Claim End Time: %s - `, p.RewardPeriods, p.ClaimMultipliers, p.ClaimEnd) + `, p.USDXMintingRewardPeriods, p.HardSupplyRewardPeriods, p.HardBorrowRewardPeriods, + p.HardDelegatorRewardPeriods, p.ClaimMultipliers, p.ClaimEnd) } // ParamKeyTable Key declaration for parameters @@ -77,7 +91,10 @@ func ParamKeyTable() params.KeyTable { // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - params.NewParamSetPair(KeyRewards, &p.RewardPeriods, validateRewardsParam), + params.NewParamSetPair(KeyUSDXMintingRewardPeriods, &p.USDXMintingRewardPeriods, validateRewardPeriodsParam), + params.NewParamSetPair(KeyHardSupplyRewardPeriods, &p.HardSupplyRewardPeriods, validateRewardPeriodsParam), + params.NewParamSetPair(KeyHardBorrowRewardPeriods, &p.HardBorrowRewardPeriods, validateRewardPeriodsParam), + params.NewParamSetPair(KeyHardDelegatorRewardPeriods, &p.HardDelegatorRewardPeriods, validateRewardPeriodsParam), params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam), params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam), } @@ -85,22 +102,27 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { // Validate checks that the parameters have valid values. func (p Params) Validate() error { + if err := validateMultipliersParam(p.ClaimMultipliers); err != nil { return err } - return validateRewardsParam(p.RewardPeriods) -} - -func validateActiveParam(i interface{}) error { - _, ok := i.(bool) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) + if err := validateRewardPeriodsParam(p.USDXMintingRewardPeriods); err != nil { + return err } - return nil + + if err := validateRewardPeriodsParam(p.HardSupplyRewardPeriods); err != nil { + return err + } + + if err := validateRewardPeriodsParam(p.HardBorrowRewardPeriods); err != nil { + return err + } + + return validateRewardPeriodsParam(p.HardDelegatorRewardPeriods) } -func validateRewardsParam(i interface{}) error { +func validateRewardPeriodsParam(i interface{}) error { rewards, ok := i.(RewardPeriods) if !ok { return fmt.Errorf("invalid parameter type: %T", i) @@ -224,9 +246,6 @@ func (m Multiplier) Validate() error { if m.MonthsLockup < 0 { return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup) } - if m.Factor == (sdk.Dec{}) { - return fmt.Errorf("claim multiplier factor not initialized for %s", m.Name) - } if m.Factor.IsNegative() { return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String()) } diff --git a/x/incentive/types/params_test.go b/x/incentive/types/params_test.go index ab33b02d..902e8de9 100644 --- a/x/incentive/types/params_test.go +++ b/x/incentive/types/params_test.go @@ -20,9 +20,12 @@ func (suite *ParamTestSuite) SetupTest() {} func (suite *ParamTestSuite) TestParamValidation() { type args struct { - rewardPeriods types.RewardPeriods - multipliers types.Multipliers - end time.Time + usdxMintingRewardPeriods types.RewardPeriods + hardSupplyRewardPeriods types.RewardPeriods + hardBorrowRewardPeriods types.RewardPeriods + hardDelegatorRewardPeriods types.RewardPeriods + multipliers types.Multipliers + end time.Time } type errArgs struct { @@ -39,9 +42,12 @@ func (suite *ParamTestSuite) TestParamValidation() { { "default", args{ - rewardPeriods: types.DefaultRewardPeriods, - multipliers: types.DefaultMultipliers, - end: types.DefaultClaimEnd, + usdxMintingRewardPeriods: types.DefaultRewardPeriods, + hardSupplyRewardPeriods: types.DefaultRewardPeriods, + hardBorrowRewardPeriods: types.DefaultRewardPeriods, + hardDelegatorRewardPeriods: types.DefaultRewardPeriods, + multipliers: types.DefaultMultipliers, + end: types.DefaultClaimEnd, }, errArgs{ expectPass: true, @@ -51,7 +57,7 @@ func (suite *ParamTestSuite) TestParamValidation() { { "valid", args{ - rewardPeriods: types.RewardPeriods{types.NewRewardPeriod( + usdxMintingRewardPeriods: types.RewardPeriods{types.NewRewardPeriod( true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))}, multipliers: types.Multipliers{ @@ -62,40 +68,22 @@ func (suite *ParamTestSuite) TestParamValidation() { types.Large, 1, sdk.MustNewDecFromStr("1.0"), ), }, - end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), + hardSupplyRewardPeriods: types.DefaultRewardPeriods, + hardBorrowRewardPeriods: types.DefaultRewardPeriods, + hardDelegatorRewardPeriods: types.DefaultRewardPeriods, + end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), }, errArgs{ expectPass: true, contains: "", }, }, - { - "invalid: empty reward factor", - args{ - rewardPeriods: types.RewardPeriods{types.NewRewardPeriod( - true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), - sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))}, - multipliers: types.Multipliers{ - types.NewMultiplier( - types.Small, 1, sdk.MustNewDecFromStr("0.25"), - ), - types.NewMultiplier( - types.Large, 1, sdk.Dec{}, - ), - }, - end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), - }, - errArgs{ - expectPass: false, - contains: "claim multiplier factor not initialized", - }, - }, } for _, tc := range testCases { suite.Run(tc.name, func() { - params := types.NewParams( - tc.args.rewardPeriods, tc.args.multipliers, tc.args.end, + params := types.NewParams(tc.args.usdxMintingRewardPeriods, tc.args.hardSupplyRewardPeriods, + tc.args.hardBorrowRewardPeriods, tc.args.hardDelegatorRewardPeriods, tc.args.multipliers, tc.args.end, ) err := params.Validate() if tc.errArgs.expectPass {