From ffbf31742fe3ee7b747ab390dcf9b1c3755d7bd4 Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Fri, 28 May 2021 14:32:19 +0100 Subject: [PATCH] Call incentive hooks before CDPs are modified in begin blocker (#897) * call hook before cdp changed * test that risky cdps accrue rewards accurately * fix typos * clarify test comments --- x/cdp/keeper/interest.go | 1 + x/incentive/keeper/cdp_test.go | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 x/incentive/keeper/cdp_test.go diff --git a/x/cdp/keeper/interest.go b/x/cdp/keeper/interest.go index 551a4fb1..a222d623 100644 --- a/x/cdp/keeper/interest.go +++ b/x/cdp/keeper/interest.go @@ -159,6 +159,7 @@ func (k Keeper) CalculateNewInterest(ctx sdk.Context, cdp types.CDP) sdk.Coin { func (k Keeper) SynchronizeInterestForRiskyCDPs(ctx sdk.Context, slice sdk.Int, targetRatio sdk.Dec, collateralType string) error { cdps := k.GetSliceOfCDPsByRatioAndType(ctx, slice, targetRatio, collateralType) for _, cdp := range cdps { + k.hooks.BeforeCDPModified(ctx, cdp) k.SynchronizeInterest(ctx, cdp) } return nil diff --git a/x/incentive/keeper/cdp_test.go b/x/incentive/keeper/cdp_test.go new file mode 100644 index 00000000..3839fe0f --- /dev/null +++ b/x/incentive/keeper/cdp_test.go @@ -0,0 +1,70 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + + cdptypes "github.com/kava-labs/kava/x/cdp/types" + "github.com/kava-labs/kava/x/incentive/types" +) + +func (suite *KeeperTestSuite) TestRiskyCDPsAccumulateRewards() { + suite.SetupWithGenState() + initialTime := suite.ctx.BlockTime() + + // Setup incentive state + collateralType := "bnb-a" + rewardsPerSecond := c(types.USDXMintingRewardDenom, 1_000_000) + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, collateralType, initialTime, initialTime.Add(4*oneYear), rewardsPerSecond)}, + nil, // hard rewards not needed + nil, + nil, // delegator rewards not needed + types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + initialTime.Add(5*oneYear), + ) + suite.keeper.SetParams(suite.ctx, params) + suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, collateralType, initialTime) + suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, collateralType, sdk.ZeroDec()) + + // Setup cdp state containing one CDP + cdpKeeper := suite.app.GetCDPKeeper() + initialCollateral := c("bnb", 1_000_000_000) + initialPrincipal := c("usdx", 100_000_000) + cdpKeeper.SetPreviousAccrualTime(suite.ctx, collateralType, suite.ctx.BlockTime()) + cdpKeeper.SetInterestFactor(suite.ctx, collateralType, sdk.OneDec()) + // add coins to user's address // TODO move this to auth genesis setup + sk := suite.app.GetSupplyKeeper() + sk.MintCoins(suite.ctx, cdptypes.ModuleName, cs(initialCollateral)) + sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], cs(initialCollateral)) + + err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], initialCollateral, initialPrincipal, collateralType) + suite.Require().NoError(err) + + // Skip ahead two blocks to accumulate both interest and usdx reward for the cdp + // Two blocks are required because the cdp begin blocker runs before incentive begin blocker. + // In the first begin block the cdp is synced, which triggers its claim to sync. But no global rewards have accumulated yet so the sync does nothing. + // Global rewards accumulate immediately after during the incentive begin blocker. + // Rewards are added to the cdp's claim in the next block when the cdp is synced. + _ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(10 * time.Minute)) + _ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers + + _ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{}) + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(10 * time.Minute)) + _ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) + + // check cdp rewards + cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], collateralType) + suite.Require().True(found) + // This additional sync adds the rewards accumulated at the end of the last begin block. + // They weren't added during the begin blocker as the incentive BB runs after the CDP BB. + suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp) + claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + + // rewards are roughly rewardsPerSecond * secondsElapsed (10mins) * num blocks (2) + suite.Require().Equal(c(types.USDXMintingRewardDenom, 1_200_001_671), claim.Reward) +}