From c2e53f2d00b474ba06774ed78545bc79de978f37 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Wed, 20 Apr 2022 13:08:57 +0200 Subject: [PATCH] Incentive module: claim savings reward (#1208) * update savings module macc balances getter * add savings keeper to incentive module * add savings keeper to incentive module #2 * savings reward syncing * claim savings reward * update txs, queries * update txs, queries #2 * update claim test * add savings keeper to incentive module in app.go * re-commit files to disk * fix: replace swap with savings when querying savings rewards * update func comment Co-authored-by: karzak --- app/app.go | 15 ++-- x/incentive/client/cli/query.go | 43 +++++++++- x/incentive/client/cli/tx.go | 36 +++++++++ x/incentive/keeper/claim.go | 45 ++++++++++- x/incentive/keeper/claim_test.go | 4 +- x/incentive/keeper/keeper.go | 4 +- x/incentive/keeper/querier.go | 55 ++++++++++++- .../keeper/rewards_borrow_accum_test.go | 14 ++-- .../keeper/rewards_delegator_accum_test.go | 14 ++-- .../keeper/rewards_delegator_init_test.go | 2 +- .../keeper/rewards_delegator_sync_test.go | 16 ++-- x/incentive/keeper/rewards_savings.go | 80 +++++++++++++++++++ .../keeper/rewards_supply_accum_test.go | 14 ++-- x/incentive/keeper/rewards_swap_accum_test.go | 14 ++-- x/incentive/keeper/rewards_swap_sync_test.go | 6 +- x/incentive/keeper/rewards_usdx_accum_test.go | 12 +-- x/incentive/keeper/unit_test.go | 6 +- x/incentive/types/expected_keepers.go | 7 ++ x/incentive/types/querier.go | 5 +- x/savings/keeper/invariants.go | 2 +- x/savings/keeper/keeper.go | 8 +- 21 files changed, 334 insertions(+), 68 deletions(-) diff --git a/app/app.go b/app/app.go index bfd3b1a3..85cfe892 100644 --- a/app/app.go +++ b/app/app.go @@ -526,6 +526,13 @@ func NewApp( app.pricefeedKeeper, app.auctionKeeper, ) + savingsKeeper := savingskeeper.NewKeeper( + appCodec, + keys[savingstypes.StoreKey], + savingsSubspace, + app.accountKeeper, + app.bankKeeper, + ) app.incentiveKeeper = incentivekeeper.NewKeeper( appCodec, keys[incentivetypes.StoreKey], @@ -536,13 +543,7 @@ func NewApp( app.accountKeeper, app.stakingKeeper, &swapKeeper, - ) - savingsKeeper := savingskeeper.NewKeeper( - appCodec, - keys[savingstypes.StoreKey], - savingsSubspace, - app.accountKeeper, - app.bankKeeper, + &savingsKeeper, ) // create committee keeper with router committeeGovRouter := govtypes.NewRouter() diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go index 28c64ac0..367ec686 100644 --- a/x/incentive/client/cli/query.go +++ b/x/incentive/client/cli/query.go @@ -24,6 +24,7 @@ const ( typeHard = "hard" typeUSDXMinting = "usdx-minting" typeSwap = "swap" + typeSavings = "savings" ) var rewardTypes = []string{typeDelegator, typeHard, typeUSDXMinting, typeSwap} @@ -64,13 +65,15 @@ func queryRewardsCmd() *cobra.Command { $ %s query %s rewards --type usdx-minting $ %s query %s rewards --type delegator $ %s query %s rewards --type swap + $ %s query %s rewards --type savings $ %s query %s rewards --type hard --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw $ %s query %s rewards --type hard --unsynced `, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, - version.AppName, types.ModuleName, version.AppName, types.ModuleName)), + version.AppName, types.ModuleName, version.AppName, types.ModuleName, + version.AppName, types.ModuleName)), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { cliCtx, err := client.GetClientQueryContext(cmd) @@ -121,6 +124,13 @@ func queryRewardsCmd() *cobra.Command { return err } return cliCtx.PrintObjectLegacy(claims) + case typeSavings: + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + claims, err := executeSavingsRewardsQuery(cliCtx, params) + if err != nil { + return err + } + return cliCtx.PrintObjectLegacy(claims) default: params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) @@ -140,6 +150,10 @@ func queryRewardsCmd() *cobra.Command { if err != nil { return err } + savingsClaims, err := executeSavingsRewardsQuery(cliCtx, params) + if err != nil { + return err + } if len(hardClaims) > 0 { if err := cliCtx.PrintObjectLegacy(hardClaims); err != nil { return err @@ -160,6 +174,11 @@ func queryRewardsCmd() *cobra.Command { return err } } + if len(savingsClaims) > 0 { + if err := cliCtx.PrintObjectLegacy(savingsClaims); err != nil { + return err + } + } } return nil }, @@ -321,3 +340,25 @@ func executeSwapRewardsQuery(cliCtx client.Context, params types.QueryRewardsPar return claims, nil } + +func executeSavingsRewardsQuery(cliCtx client.Context, params types.QueryRewardsParams) (types.SavingsClaims, error) { + bz, err := cliCtx.LegacyAmino.MarshalJSON(params) + if err != nil { + return types.SavingsClaims{}, err + } + + route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetSavingsRewards) + res, height, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return types.SavingsClaims{}, err + } + + cliCtx = cliCtx.WithHeight(height) + + var claims types.SavingsClaims + if err := cliCtx.LegacyAmino.UnmarshalJSON(res, &claims); err != nil { + return types.SavingsClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err) + } + + return claims, nil +} diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go index ac4744c3..a692e01d 100644 --- a/x/incentive/client/cli/tx.go +++ b/x/incentive/client/cli/tx.go @@ -29,6 +29,7 @@ func GetTxCmd() *cobra.Command { getCmdClaimHard(), getCmdClaimDelegator(), getCmdClaimSwap(), + getCmdClaimSavings(), } for _, cmd := range cmds { @@ -172,3 +173,38 @@ func getCmdClaimSwap() *cobra.Command { } return cmd } + +func getCmdClaimSavings() *cobra.Command { + var denomsToClaim map[string]string + + cmd := &cobra.Command{ + Use: "claim-savings", + Short: "claim sender's savings rewards using given multipliers", + Long: `Claim sender's outstanding savings rewards using given multipliers`, + Example: strings.Join([]string{ + fmt.Sprintf(` $ %s tx %s claim-savings --%s swp=large --%s ukava=small`, version.AppName, types.ModuleName, multiplierFlag, multiplierFlag), + fmt.Sprintf(` $ %s tx %s claim-savings --%s swp=large,ukava=small`, version.AppName, types.ModuleName, multiplierFlag), + }, "\n"), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + sender := cliCtx.GetFromAddress() + selections := types.NewSelectionsFromMap(denomsToClaim) + + msg := types.NewMsgClaimSavingsReward(sender.String(), selections) + if err := msg.ValidateBasic(); err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), &msg) + }, + } + cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup") + if err := cmd.MarkFlagRequired(multiplierFlag); err != nil { + panic(err) + } + return cmd +} diff --git a/x/incentive/keeper/claim.go b/x/incentive/keeper/claim.go index c2a66ea3..868e6f5b 100644 --- a/x/incentive/keeper/claim.go +++ b/x/incentive/keeper/claim.go @@ -210,6 +210,49 @@ func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress, // ClaimSavingsReward is a stub method for MsgServer interface compliance func (k Keeper) ClaimSavingsReward(ctx sdk.Context, owner, receiver sdk.AccAddress, denom string, multiplierName string) error { - // TODO: implement savings claim logic + multiplier, found := k.GetMultiplierByDenom(ctx, denom, multiplierName) + if !found { + return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", denom, multiplierName) + } + + claimEnd := k.GetClaimEnd(ctx) + + if ctx.BlockTime().After(claimEnd) { + return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd) + } + + k.SynchronizeSavingsClaim(ctx, owner) + + syncedClaim, found := k.GetSavingsClaim(ctx, owner) + if !found { + return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner) + } + + amt := syncedClaim.Reward.AmountOf(denom) + + claimingCoins := sdk.NewCoins(sdk.NewCoin(denom, amt)) + rewardCoins := sdk.NewCoins(sdk.NewCoin(denom, amt.ToDec().Mul(multiplier.Factor).RoundInt())) + if rewardCoins.IsZero() { + return types.ErrZeroClaim + } + length := k.GetPeriodLength(ctx.BlockTime(), multiplier.MonthsLockup) + + err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length) + if err != nil { + return err + } + + // remove claimed coins (NOT reward coins) + syncedClaim.Reward = syncedClaim.Reward.Sub(claimingCoins) + k.SetSavingsClaim(ctx, syncedClaim) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeClaim, + sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()), + sdk.NewAttribute(types.AttributeKeyClaimAmount, claimingCoins.String()), + sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()), + ), + ) return nil } diff --git a/x/incentive/keeper/claim_test.go b/x/incentive/keeper/claim_test.go index fe7a96db..9c42a4a8 100644 --- a/x/incentive/keeper/claim_test.go +++ b/x/incentive/keeper/claim_test.go @@ -36,7 +36,7 @@ func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() { }, }, } - suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -70,7 +70,7 @@ func (suite *ClaimTests) TestCannotClaimAfterEndTime() { ClaimEnd: endTime, }, } - suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil) suite.ctx = suite.ctx.WithBlockTime(endTime.Add(time.Nanosecond)) diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 4597ee86..a9385725 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -21,13 +21,14 @@ type Keeper struct { hardKeeper types.HardKeeper stakingKeeper types.StakingKeeper swapKeeper types.SwapKeeper + savingsKeeper types.SavingsKeeper } // NewKeeper creates a new keeper func NewKeeper( cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, - swpk types.SwapKeeper, + swpk types.SwapKeeper, svk types.SavingsKeeper, ) Keeper { if !paramstore.HasKeyTable() { @@ -44,6 +45,7 @@ func NewKeeper( hardKeeper: hk, stakingKeeper: stk, swapKeeper: swpk, + savingsKeeper: svk, } } diff --git a/x/incentive/keeper/querier.go b/x/incentive/keeper/querier.go index 91f87452..3f94d54f 100644 --- a/x/incentive/keeper/querier.go +++ b/x/incentive/keeper/querier.go @@ -25,7 +25,8 @@ func NewQuerier(k Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { return queryGetDelegatorRewards(ctx, req, k, legacyQuerierCdc) case types.QueryGetSwapRewards: return queryGetSwapRewards(ctx, req, k, legacyQuerierCdc) - + case types.QueryGetSavingsRewards: + return queryGetSavingsRewards(ctx, req, k, legacyQuerierCdc) case types.QueryGetRewardFactors: return queryGetRewardFactors(ctx, req, k, legacyQuerierCdc) default: @@ -215,6 +216,51 @@ func queryGetSwapRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper, legac return bz, nil } +func queryGetSavingsRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) { + var params types.QueryRewardsParams + err := legacyQuerierCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + owner := len(params.Owner) > 0 + + var claims types.SavingsClaims + switch { + case owner: + claim, found := k.GetSavingsClaim(ctx, params.Owner) + if found { + claims = append(claims, claim) + } + default: + claims = k.GetAllSavingsClaims(ctx) + } + + var paginatedClaims types.SavingsClaims + startH, endH := client.Paginate(len(claims), params.Page, params.Limit, 100) + if startH < 0 || endH < 0 { + paginatedClaims = types.SavingsClaims{} + } else { + paginatedClaims = claims[startH:endH] + } + + if !params.Unsynchronized { + for i, claim := range paginatedClaims { + syncedClaim, found := k.GetSynchronizedSavingsClaim(ctx, claim.Owner) + if !found { + panic("previously found claim should still be found") + } + paginatedClaims[i] = syncedClaim + } + } + + // Marshal claims + bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, paginatedClaims) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return bz, nil +} + func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) { var usdxFactors types.RewardIndexes @@ -247,12 +293,19 @@ func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper, leg return false }) + var savingsFactors types.MultiRewardIndexes + k.IterateSavingsRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { + savingsFactors = savingsFactors.With(denom, indexes) + return false + }) + response := types.NewQueryGetRewardFactorsResponse( usdxFactors, supplyFactors, borrowFactors, delegatorFactors, swapFactors, + savingsFactors, ) bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, response) diff --git a/x/incentive/keeper/rewards_borrow_accum_test.go b/x/incentive/keeper/rewards_borrow_accum_test.go index 262cb347..d426e15c 100644 --- a/x/incentive/keeper/rewards_borrow_accum_test.go +++ b/x/incentive/keeper/rewards_borrow_accum_test.go @@ -39,7 +39,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateUpdatedWhenBlockTimeHasIncre denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) suite.storeGlobalBorrowIndexes(types.MultiRewardIndexes{ { @@ -91,7 +91,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateUnchangedWhenBlockTimeHasNot denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -136,7 +136,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenSourceSharesAre denom := "bnb" hardKeeper := newFakeHardKeeper() // zero total borrows - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -182,7 +182,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateAddedWhenStateDoesNotExist() denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -225,7 +225,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoPanicWhenStateDoesNotExist() { denom := "bnb" hardKeeper := newFakeHardKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -253,7 +253,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenBeforeStartTime denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -299,7 +299,7 @@ func (suite *AccumulateBorrowRewardsTests) TestPanicWhenCurrentTimeLessThanPrevi denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_delegator_accum_test.go b/x/incentive/keeper/rewards_delegator_accum_test.go index 4f176fe6..d2d54b48 100644 --- a/x/incentive/keeper/rewards_delegator_accum_test.go +++ b/x/incentive/keeper/rewards_delegator_accum_test.go @@ -37,7 +37,7 @@ func TestAccumulateDelegatorRewards(t *testing.T) { func (suite *AccumulateDelegatorRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) suite.storeGlobalDelegatorIndexes(types.MultiRewardIndexes{ { @@ -88,7 +88,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateUpdatedWhenBlockTimeHasIn func (suite *AccumulateDelegatorRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -132,7 +132,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateUnchangedWhenBlockTimeHas func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() { stakingKeeper := newFakeStakingKeeper() // zero total bonded - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -177,7 +177,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenSourceShares func (suite *AccumulateDelegatorRewardsTests) TestStateAddedWhenStateDoesNotExist() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -219,7 +219,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateAddedWhenStateDoesNotExis func (suite *AccumulateDelegatorRewardsTests) TestNoPanicWhenStateDoesNotExist() { stakingKeeper := newFakeStakingKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -246,7 +246,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoPanicWhenStateDoesNotExist() func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenBeforeStartTime() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -291,7 +291,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenBeforeStartT func (suite *AccumulateDelegatorRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_delegator_init_test.go b/x/incentive/keeper/rewards_delegator_init_test.go index 95829552..17fbdf62 100644 --- a/x/incentive/keeper/rewards_delegator_init_test.go +++ b/x/incentive/keeper/rewards_delegator_init_test.go @@ -58,7 +58,7 @@ func (suite *InitializeDelegatorRewardTests) TestClaimIsSyncedAndIndexesAreSetWh DelegatorShares: d("1000"), }}, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, sk, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, sk, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ diff --git a/x/incentive/keeper/rewards_delegator_sync_test.go b/x/incentive/keeper/rewards_delegator_sync_test.go index d20db0d6..fdb77b02 100644 --- a/x/incentive/keeper/rewards_delegator_sync_test.go +++ b/x/incentive/keeper/rewards_delegator_sync_test.go @@ -37,7 +37,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGl delegator := arbitraryAddress() stakingKeeper := &fakeStakingKeeper{} // use an empty staking keeper that returns no delegations - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -58,7 +58,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGl func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlobalFactorIncreased() { delegator := arbitraryAddress() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, &fakeStakingKeeper{}, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, &fakeStakingKeeper{}, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -97,7 +97,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFac unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -142,7 +142,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewReward unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -192,7 +192,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFac unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -298,7 +298,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenValAddrIsNil() unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) suite.Equal( d("11"), // delegation to bonded validators @@ -341,7 +341,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenExcludingAVali unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) suite.Equal( d("10"), @@ -384,7 +384,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenIncludingAVali unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil) suite.Equal( d("111"), diff --git a/x/incentive/keeper/rewards_savings.go b/x/incentive/keeper/rewards_savings.go index 6cb40077..44e288ff 100644 --- a/x/incentive/keeper/rewards_savings.go +++ b/x/incentive/keeper/rewards_savings.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/incentive/types" @@ -35,3 +37,81 @@ func (k Keeper) AccumulateSavingsRewards(ctx sdk.Context, rewardPeriod types.Mul k.SetSavingsRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes) } } + +func (k Keeper) SynchronizeSavingsClaim(ctx sdk.Context, owner sdk.AccAddress) { + deposit, found := k.savingsKeeper.GetDeposit(ctx, owner) + if !found { + return + } + k.SynchronizeSavingsReward(ctx, deposit) +} + +// SynchronizeSavingsReward updates the claim object by adding any accumulated rewards +// and updating the reward index value +func (k Keeper) SynchronizeSavingsReward(ctx sdk.Context, deposit savingstypes.Deposit) { + claim, found := k.GetSavingsClaim(ctx, deposit.Depositor) + if !found { + return + } + + // Source shares for savings deposits is the deposit amount + for _, coin := range deposit.Amount { + claim = k.synchronizeSingleSavingsReward(ctx, claim, coin.Denom, coin.Amount.ToDec()) + } + k.SetSavingsClaim(ctx, claim) +} + +// synchronizeSingleSavingsReward synchronizes a single rewarded savings denom in a s claim. +// It returns the claim without setting in the store. +// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong. +func (k Keeper) synchronizeSingleSavingsReward(ctx sdk.Context, claim types.SavingsClaim, denom string, sourceShares sdk.Dec) types.SavingsClaim { + globalRewardIndexes, found := k.GetSavingsRewardIndexes(ctx, denom) + if !found { + // The global factor is only not found if + // - the savings denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit) + // - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist) + // If not found we could either skip this sync, or assume the global factor is zero. + // Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms. + // And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards. + return claim + } + + userRewardIndexes, found := claim.RewardIndexes.Get(denom) + if !found { + // Normally the reward indexes should always be found. + // But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that supplied denom. + // So given the reward period was just added, assume the starting value for any global reward indexes, which is an empty slice. + userRewardIndexes = types.RewardIndexes{} + } + + newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, sourceShares) + if err != nil { + // Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards. + // This panics if a global reward factor decreases or disappears between the old and new indexes. + panic(fmt.Sprintf("corrupted global reward indexes found: %v", err)) + } + + claim.Reward = claim.Reward.Add(newRewards...) + claim.RewardIndexes = claim.RewardIndexes.With(denom, globalRewardIndexes) + + return claim +} + +// GetSynchronizedSavingsClaim fetches a savings claim from the store and syncs rewards for all rewarded pools. +func (k Keeper) GetSynchronizedSavingsClaim(ctx sdk.Context, owner sdk.AccAddress) (types.SavingsClaim, bool) { + claim, found := k.GetSavingsClaim(ctx, owner) + if !found { + return types.SavingsClaim{}, false + } + + deposit, found := k.savingsKeeper.GetDeposit(ctx, owner) + if !found { + return types.SavingsClaim{}, false + } + + for _, coin := range deposit.Amount { + claim = k.synchronizeSingleSavingsReward(ctx, claim, coin.Denom, coin.Amount.ToDec()) + } + + return claim, true +} diff --git a/x/incentive/keeper/rewards_supply_accum_test.go b/x/incentive/keeper/rewards_supply_accum_test.go index 2683d186..b3e134e6 100644 --- a/x/incentive/keeper/rewards_supply_accum_test.go +++ b/x/incentive/keeper/rewards_supply_accum_test.go @@ -38,7 +38,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateUpdatedWhenBlockTimeHasIncre denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) suite.storeGlobalSupplyIndexes(types.MultiRewardIndexes{ { @@ -90,7 +90,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateUnchangedWhenBlockTimeHasNot denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -135,7 +135,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenSourceSharesAre denom := "bnb" hardKeeper := newFakeHardKeeper() // zero total supplys - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -181,7 +181,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateAddedWhenStateDoesNotExist() denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -224,7 +224,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoPanicWhenStateDoesNotExist() { denom := "bnb" hardKeeper := newFakeHardKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -252,7 +252,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenBeforeStartTime denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -298,7 +298,7 @@ func (suite *AccumulateSupplyRewardsTests) TestPanicWhenCurrentTimeLessThanPrevi denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_swap_accum_test.go b/x/incentive/keeper/rewards_swap_accum_test.go index 0f91413a..0bc082b6 100644 --- a/x/incentive/keeper/rewards_swap_accum_test.go +++ b/x/incentive/keeper/rewards_swap_accum_test.go @@ -37,7 +37,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{ { @@ -89,7 +89,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -134,7 +134,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenSourceSharesAreZe pool := "btc:usdx" swapKeeper := newFakeSwapKeeper() // no pools, so no source shares - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -180,7 +180,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() { pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) period := types.NewMultiRewardPeriod( true, @@ -223,7 +223,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() { pool := "btc:usdx" swapKeeper := newFakeSwapKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) period := types.NewMultiRewardPeriod( true, @@ -251,7 +251,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenBeforeStartTime() pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -297,7 +297,7 @@ func (suite *AccumulateSwapRewardsTests) TestPanicWhenCurrentTimeLessThanPreviou pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_swap_sync_test.go b/x/incentive/keeper/rewards_swap_sync_test.go index 89ce73e0..a2901967 100644 --- a/x/incentive/keeper/rewards_swap_sync_test.go +++ b/x/incentive/keeper/rewards_swap_sync_test.go @@ -322,7 +322,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUnchangedWhenNo swapKeeper := newFakeSwapKeeper(). addDeposit(poolID_1, owner, i(1e9)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -355,7 +355,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss owner := arbitraryAddress() // owner has no shares in any pool - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, newFakeSwapKeeper()) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, newFakeSwapKeeper(), nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -415,7 +415,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss swapKeeper := newFakeSwapKeeper(). addDeposit(poolID_1, owner, i(1e9)). addDeposit(poolID_2, owner, i(1e9)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ diff --git a/x/incentive/keeper/rewards_usdx_accum_test.go b/x/incentive/keeper/rewards_usdx_accum_test.go index ad166e2c..0d354094 100644 --- a/x/incentive/keeper/rewards_usdx_accum_test.go +++ b/x/incentive/keeper/rewards_usdx_accum_test.go @@ -34,7 +34,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) suite.storeGlobalUSDXIndexes(types.RewardIndexes{ { @@ -68,7 +68,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -104,7 +104,7 @@ func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenSourceSharesAreZe cType := "bnb-a" cdpKeeper := newFakeCDPKeeper() // zero total borrows - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -141,7 +141,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateAddedWhenStateDoesNotExist() { cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) period := types.NewRewardPeriod( true, @@ -174,7 +174,7 @@ func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenBeforeStartTime() cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -211,7 +211,7 @@ func (suite *AccumulateUSDXRewardsTests) TestPanicWhenCurrentTimeLessThanPreviou cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime) diff --git a/x/incentive/keeper/unit_test.go b/x/incentive/keeper/unit_test.go index f6ec0b16..fd6bc295 100644 --- a/x/incentive/keeper/unit_test.go +++ b/x/incentive/keeper/unit_test.go @@ -60,7 +60,7 @@ func (suite *unitTester) SetupSuite() { func (suite *unitTester) SetupTest() { suite.ctx = NewTestContext(suite.incentiveStoreKey) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil) } func (suite *unitTester) TearDownTest() { @@ -68,8 +68,8 @@ func (suite *unitTester) TearDownTest() { suite.ctx = sdk.Context{} } -func (suite *unitTester) NewKeeper(paramSubspace types.ParamSubspace, bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper) keeper.Keeper { - return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, bk, cdpk, hk, ak, stk, swk) +func (suite *unitTester) NewKeeper(paramSubspace types.ParamSubspace, bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper, svk types.SavingsKeeper) keeper.Keeper { + return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, bk, cdpk, hk, ak, stk, swk, svk) } func (suite *unitTester) storeGlobalBorrowIndexes(indexes types.MultiRewardIndexes) { diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index d0f6fb5a..3d9d5716 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -7,6 +7,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" cdptypes "github.com/kava-labs/kava/x/cdp/types" hardtypes "github.com/kava-labs/kava/x/hard/types" + savingstypes "github.com/kava-labs/kava/x/savings/types" ) // ParamSubspace defines the expected Subspace interfacace @@ -56,6 +57,12 @@ type SwapKeeper interface { GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (shares sdk.Int, found bool) } +// SavingsKeeper defines the required methods needed by this module's keeper +type SavingsKeeper interface { + GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (savingstypes.Deposit, bool) + GetSavingsModuleAccountBalances(ctx sdk.Context) sdk.Coins +} + // AccountKeeper expected interface for the account keeper (noalias) type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI diff --git a/x/incentive/types/querier.go b/x/incentive/types/querier.go index 5da6d23e..08c99cf8 100644 --- a/x/incentive/types/querier.go +++ b/x/incentive/types/querier.go @@ -10,6 +10,7 @@ const ( QueryGetUSDXMintingRewards = "usdx-minting-rewards" QueryGetDelegatorRewards = "delegator-rewards" QueryGetSwapRewards = "swap-rewards" + QueryGetSavingsRewards = "savings-rewards" QueryGetRewardFactors = "reward-factors" QueryGetParams = "parameters" @@ -44,16 +45,18 @@ type QueryGetRewardFactorsResponse struct { HardBorrowRewardFactors MultiRewardIndexes `json:"hard_borrow_reward_factors" yaml:"hard_borrow_reward_factors"` DelegatorRewardFactors MultiRewardIndexes `json:"delegator_reward_factors" yaml:"delegator_reward_factors"` SwapRewardFactors MultiRewardIndexes `json:"swap_reward_factors" yaml:"swap_reward_factors"` + SavingsRewardFactors MultiRewardIndexes `json:"savings_reward_factors" yaml:"savings_reward_factors"` } // NewQueryGetRewardFactorsResponse returns a new instance of QueryAllRewardFactorsResponse func NewQueryGetRewardFactorsResponse(usdxMintingFactors RewardIndexes, supplyFactors, - hardBorrowFactors, delegatorFactors, swapFactors MultiRewardIndexes) QueryGetRewardFactorsResponse { + hardBorrowFactors, delegatorFactors, swapFactors, savingsFactors MultiRewardIndexes) QueryGetRewardFactorsResponse { return QueryGetRewardFactorsResponse{ USDXMintingRewardFactors: usdxMintingFactors, HardSupplyRewardFactors: supplyFactors, HardBorrowRewardFactors: hardBorrowFactors, DelegatorRewardFactors: delegatorFactors, SwapRewardFactors: swapFactors, + SavingsRewardFactors: savingsFactors, } } diff --git a/x/savings/keeper/invariants.go b/x/savings/keeper/invariants.go index 0f65c3f2..e963b597 100644 --- a/x/savings/keeper/invariants.go +++ b/x/savings/keeper/invariants.go @@ -51,7 +51,7 @@ func SolvencyInvariant(k Keeper) sdk.Invariant { message := sdk.FormatInvariant(types.ModuleName, "module solvency broken", "total deposited amount does not match module account") return func(ctx sdk.Context) (string, bool) { - balance := k.bankKeeper.GetAllBalances(ctx, k.GetSavingsModuleAccount(ctx).GetAddress()) + balance := k.GetSavingsModuleAccountBalances(ctx) deposited := sdk.Coins{} k.IterateDeposits(ctx, func(deposit types.Deposit) bool { diff --git a/x/savings/keeper/keeper.go b/x/savings/keeper/keeper.go index 32a1e893..86b3a2b7 100644 --- a/x/savings/keeper/keeper.go +++ b/x/savings/keeper/keeper.go @@ -6,7 +6,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/tendermint/tendermint/libs/log" @@ -51,9 +50,10 @@ func (k *Keeper) SetHooks(hooks types.MultiSavingsHooks) *Keeper { return k } -// GetSavingsModuleAccount returns the savings ModuleAccount -func (k Keeper) GetSavingsModuleAccount(ctx sdk.Context) authtypes.ModuleAccountI { - return k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName) +// GetSavingsModuleAccountBalances returns the savings module account balances +func (k Keeper) GetSavingsModuleAccountBalances(ctx sdk.Context) sdk.Coins { + savingMacc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName) + return k.bankKeeper.GetAllBalances(ctx, savingMacc.GetAddress()) } // GetDeposit returns a deposit from the store for a particular depositor address, deposit denom