From 754c018f04af022b3c8220053f3915ec32cf1b24 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Wed, 3 Feb 2021 23:23:53 +0100 Subject: [PATCH] Incentive module multiple reward denom tests (#788) * types: multiple rewards * supply-side reward keeper methods * remove legacy comments * update hard claim reward to coins type * borrow-side reward keeper methods * update claim payout to sdk.Coins * make tests compile * fix genesis validation for compile * comment out failing tests * fix ! found logic * accumulate hard supply rewards (single) * sync hard supply rewards (single) * update hard borrow/supply index denoms * accumulate hard borrow rewards (single) * sync hard borrow rewards (single) * sync hard delegator rewards * payout hard claim (single) * accumulate hard supply rewards (multiple) * sync hard supply rewards (multiple) * update hard supply index denoms (multiple) * update hard borrow index denoms (multiple) * accumulate hard borrow rewards (multiple) * sync hard borrow rewards (multiple) * fix denom * remove comment * fix merge * payout hard reward (multiple) Co-authored-by: karzak --- x/incentive/keeper/payout.go | 3 + x/incentive/keeper/payout_test.go | 413 +++-- x/incentive/keeper/rewards.go | 56 +- x/incentive/keeper/rewards_test.go | 2723 ++++++++++++++++------------ x/incentive/types/claims.go | 10 + 5 files changed, 1853 insertions(+), 1352 deletions(-) diff --git a/x/incentive/keeper/payout.go b/x/incentive/keeper/payout.go index b9204221..7776b347 100644 --- a/x/incentive/keeper/payout.go +++ b/x/incentive/keeper/payout.go @@ -107,6 +107,9 @@ func (k Keeper) ClaimHardReward(ctx sdk.Context, addr sdk.AccAddress, multiplier } rewardCoins = append(rewardCoins, sdk.NewCoin(coin.Denom, rewardAmount)) } + if rewardCoins.IsZero() { + return types.ErrZeroClaim + } length, err := k.GetPeriodLength(ctx, multiplier) if err != nil { return err diff --git a/x/incentive/keeper/payout_test.go b/x/incentive/keeper/payout_test.go index 4196d28f..cb58489d 100644 --- a/x/incentive/keeper/payout_test.go +++ b/x/incentive/keeper/payout_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/kava-labs/kava/app" cdptypes "github.com/kava-labs/kava/x/cdp/types" + "github.com/kava-labs/kava/x/hard" "github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/kavadist" validatorvesting "github.com/kava-labs/kava/x/validator-vesting" @@ -153,179 +154,281 @@ func (suite *KeeperTestSuite) TestPayoutUSDXMintingClaim() { } } -// func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() { -// type args struct { -// deposit sdk.Coins -// borrow sdk.Coins -// rewardsPerSecond sdk.Coin -// initialTime time.Time -// multipliers types.Multipliers -// multiplier types.MultiplierName -// timeElapsed int64 -// expectedReward sdk.Coin -// expectedPeriods vesting.Periods -// isPeriodicVestingAccount bool -// } -// type errArgs struct { -// expectPass bool -// contains string -// } -// type test struct { -// name string -// args args -// errArgs errArgs -// } -// testCases := []test{ -// { -// "valid 1 day", -// args{ -// deposit: cs(c("bnb", 10000000000)), -// borrow: cs(c("bnb", 5000000000)), -// rewardsPerSecond: c("hard", 122354), -// initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), -// multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, -// multiplier: types.MultiplierName("large"), -// timeElapsed: 86400, -// expectedReward: c("hard", 21142771200), // 10571385600 (deposit reward) + 10571385600 (borrow reward) -// expectedPeriods: vesting.Periods{vesting.Period{Length: 31536000, Amount: cs(c("hard", 21142771200))}}, -// isPeriodicVestingAccount: true, -// }, -// errArgs{ -// expectPass: true, -// contains: "", -// }, -// }, -// { -// "invalid zero rewards", -// args{ -// deposit: cs(c("bnb", 10000000000)), -// borrow: cs(c("bnb", 5000000000)), -// rewardsPerSecond: c("hard", 0), -// initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), -// multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, -// multiplier: types.MultiplierName("large"), -// timeElapsed: 86400, -// expectedReward: sdk.Coin{}, -// expectedPeriods: vesting.Periods{}, -// isPeriodicVestingAccount: false, -// }, -// errArgs{ -// expectPass: false, -// contains: "claim amount rounds to zero", -// }, -// }, -// } +func (suite *KeeperTestSuite) TestPayoutHardLiquidityProviderClaim() { + type args struct { + deposit sdk.Coins + borrow sdk.Coins + rewardsPerSecond sdk.Coins + initialTime time.Time + multipliers types.Multipliers + multiplier types.MultiplierName + timeElapsed int64 + expectedRewards sdk.Coins + expectedPeriods vesting.Periods + isPeriodicVestingAccount bool + } + type errArgs struct { + expectPass bool + contains string + } + type test struct { + name string + args args + errArgs errArgs + } + testCases := []test{ + { + "single reward denom: valid 1 day", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 86400, + expectedRewards: cs(c("hard", 21142771200)), // 10571385600 (deposit reward) + 10571385600 (borrow reward) + expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200))}}, + isPeriodicVestingAccount: true, + }, + errArgs{ + expectPass: true, + contains: "", + }, + }, + { + "single reward denom: valid 10 days", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 864000, + expectedRewards: cs(c("hard", 211427712000)), // 105713856000 (deposit reward) + 105713856000 (borrow reward) + expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712000))}}, + isPeriodicVestingAccount: true, + }, + errArgs{ + expectPass: true, + contains: "", + }, + }, + { + "invalid zero rewards", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 0)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 86400, + expectedRewards: cs(c("hard", 0)), + expectedPeriods: vesting.Periods{}, + isPeriodicVestingAccount: false, + }, + errArgs{ + expectPass: false, + contains: "claim amount rounds to zero", + }, + }, + { + "multiple reward denoms: valid 1 day", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 86400, + expectedRewards: cs(c("hard", 21142771200), c("ukava", 21142771200)), // 10571385600 (deposit reward) + 10571385600 (borrow reward) + expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200), c("ukava", 21142771200))}}, + isPeriodicVestingAccount: true, + }, + errArgs{ + expectPass: true, + contains: "", + }, + }, + { + "multiple reward denoms: valid 10 days", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 864000, + expectedRewards: cs(c("hard", 211427712000), c("ukava", 211427712000)), // 105713856000 (deposit reward) + 105713856000 (borrow reward) + expectedPeriods: vesting.Periods{vesting.Period{Length: 32140800, Amount: cs(c("hard", 211427712000), c("ukava", 211427712000))}}, + isPeriodicVestingAccount: true, + }, + errArgs{ + expectPass: true, + contains: "", + }, + }, + { + "multiple reward denoms with different rewards per second: valid 1 day", + args{ + deposit: cs(c("bnb", 10000000000)), + borrow: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 222222)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))}, + multiplier: types.MultiplierName("large"), + timeElapsed: 86400, + expectedRewards: cs(c("hard", 21142771200), c("ukava", 38399961600)), + expectedPeriods: vesting.Periods{vesting.Period{Length: 32918400, Amount: cs(c("hard", 21142771200), c("ukava", 38399961600))}}, + isPeriodicVestingAccount: true, + }, + errArgs{ + expectPass: true, + contains: "", + }, + }, + } -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// suite.SetupWithGenState() -// suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) -// // setup kavadist state -// sk := suite.app.GetSupplyKeeper() -// err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("hard", 1000000000000))) -// suite.Require().NoError(err) + // setup kavadist state + sk := suite.app.GetSupplyKeeper() + err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("hard", 1000000000000000000), c("ukava", 1000000000000000000))) + suite.Require().NoError(err) -// // Set up generic reward periods -// var multiRewardPeriods types.MultiRewardPeriods -// var rewardPeriods types.RewardPeriods -// for _, coin := range tc.args.deposit { -// rewardPeriod := types.NewRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) -// rewardPeriods = append(rewardPeriods, rewardPeriod) -// multiRewardPeriod := types.NewMultiRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond)) -// multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) -// } + // Set up generic reward periods + var multiRewardPeriods types.MultiRewardPeriods + var rewardPeriods types.RewardPeriods + for _, coin := range tc.args.deposit { + if len(tc.args.rewardsPerSecond) > 0 { + rewardPeriod := types.NewRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0]) + rewardPeriods = append(rewardPeriods, rewardPeriod) + } + multiRewardPeriod := types.NewMultiRewardPeriod(true, coin.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) + multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) + } -// // Set up incentive state -// params := types.NewParams( -// rewardPeriods, multiRewardPeriods, multiRewardPeriods, rewardPeriods, -// tc.args.multipliers, -// tc.args.initialTime.Add(time.Hour*24*365*5), -// ) -// suite.keeper.SetParams(suite.ctx, params) + // Set up generic reward periods + params := types.NewParams( + rewardPeriods, multiRewardPeriods, multiRewardPeriods, 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 _, coin := range tc.args.deposit { -// suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, coin.Denom, defaultRewardIndexes) -// } -// for _, coin := range tc.args.borrow { -// suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, coin.Denom, defaultRewardIndexes) -// } + // Set each denom's previous accrual time and supply reward factor + if len(tc.args.rewardsPerSecond) > 0 { + for _, coin := range tc.args.deposit { + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, coin.Denom, rewardIndexes) + } + } -// hardKeeper := suite.app.GetHardKeeper() -// userAddr := suite.addrs[3] + // Set each denom's previous accrual time and borrow reward factor + if len(tc.args.rewardsPerSecond) > 0 { + for _, coin := range tc.args.borrow { + suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, coin.Denom, rewardIndexes) + } + } -// // User deposits -// err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.deposit) -// suite.Require().NoError(err) + hardKeeper := suite.app.GetHardKeeper() + userAddr := suite.addrs[3] -// // User borrows -// err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.borrow) -// suite.Require().NoError(err) + // User deposits and borrows + err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.deposit) + suite.Require().NoError(err) + err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.borrow) + suite.Require().NoError(err) -// // Check that Hard hooks initialized a HardLiquidityProviderClaim that has 0 rewards -// claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) -// suite.Require().True(found) -// for _, coin := range tc.args.deposit { -// suite.Require().Equal(sdk.ZeroInt(), claim.Reward.AmountOf(coin.Denom)) -// } + // Check that Hard hooks initialized a HardLiquidityProviderClaim that has 0 rewards + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, coin := range tc.args.deposit { + suite.Require().Equal(sdk.ZeroInt(), claim.Reward.AmountOf(coin.Denom)) + } -// // Set up future runtime context -// runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.timeElapsed), 0) -// runCtx := suite.ctx.WithBlockTime(runAtTime) + // Set up future runtime context + runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.timeElapsed), 0) + runCtx := suite.ctx.WithBlockTime(runAtTime) -// // Run Hard begin blocker -// hard.BeginBlocker(runCtx, suite.hardKeeper) + // Run Hard begin blocker + hard.BeginBlocker(runCtx, suite.hardKeeper) -// // Accumulate supply rewards for each deposit denom -// for _, coin := range tc.args.deposit { -// rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, coin.Denom) -// suite.Require().True(found) -// err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod) -// suite.Require().NoError(err) -// } + // Accumulate supply rewards for each deposit denom + for _, coin := range tc.args.deposit { + rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, coin.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod) + suite.Require().NoError(err) + } -// // Accumulate borrow rewards for each deposit denom -// for _, coin := range tc.args.borrow { -// rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, coin.Denom) -// suite.Require().True(found) -// err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod) -// suite.Require().NoError(err) -// } + // Accumulate borrow rewards for each deposit denom + for _, coin := range tc.args.borrow { + rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, coin.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod) + suite.Require().NoError(err) + } -// // Fetch pre-claim balances -// ak := suite.app.GetAccountKeeper() -// preClaimAcc := ak.GetAccount(runCtx, suite.addrs[3]) + // Sync hard supply rewards + deposit, found := suite.hardKeeper.GetDeposit(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit) -// err = suite.keeper.ClaimHardReward(runCtx, suite.addrs[3], tc.args.multiplier) -// if tc.errArgs.expectPass { -// suite.Require().NoError(err) + // Sync hard borrow rewards + borrow, found := suite.hardKeeper.GetBorrow(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow) -// // Check that user's balance has increased by expected reward amount -// postClaimAcc := ak.GetAccount(suite.ctx, suite.addrs[3]) -// suite.Require().Equal(preClaimAcc.GetCoins().Add(tc.args.expectedReward), postClaimAcc.GetCoins()) + // Fetch pre-claim balances + ak := suite.app.GetAccountKeeper() + preClaimAcc := ak.GetAccount(runCtx, suite.addrs[3]) -// if tc.args.isPeriodicVestingAccount { -// vacc, ok := postClaimAcc.(*vesting.PeriodicVestingAccount) -// suite.Require().True(ok) -// suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods) -// } + err = suite.keeper.ClaimHardReward(runCtx, suite.addrs[3], tc.args.multiplier) + if tc.errArgs.expectPass { + suite.Require().NoError(err) -// // Check that the claim's reward amount has been reset to 0 -// claim, found := suite.keeper.GetHardLiquidityProviderClaim(runCtx, suite.addrs[3]) -// suite.Require().True(found) -// suite.Require().Equal(c("hard", 0), claim.Reward) -// } else { -// suite.Require().Error(err) -// suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) -// } -// }) -// } -// } + // Check that user's balance has increased by expected reward amount + postClaimAcc := ak.GetAccount(suite.ctx, suite.addrs[3]) + suite.Require().Equal(preClaimAcc.GetCoins().Add(tc.args.expectedRewards...), postClaimAcc.GetCoins()) + + if tc.args.isPeriodicVestingAccount { + vacc, ok := postClaimAcc.(*vesting.PeriodicVestingAccount) + suite.Require().True(ok) + suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods) + } + + // Check that each claim reward coin's amount has been reset to 0 + claim, found := suite.keeper.GetHardLiquidityProviderClaim(runCtx, suite.addrs[3]) + suite.Require().True(found) + for _, claimRewardCoin := range claim.Reward { + suite.Require().Equal(c(claimRewardCoin.Denom, 0), claimRewardCoin) + } + } else { + suite.Require().Error(err) + suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) + } + }) + } +} func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() { type accountArgs struct { diff --git a/x/incentive/keeper/rewards.go b/x/incentive/keeper/rewards.go index e5a92265..617332ae 100644 --- a/x/incentive/keeper/rewards.go +++ b/x/incentive/keeper/rewards.go @@ -301,16 +301,17 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D continue } + userRewardIndexIndex, foundUserRewardIndexIndex := claim.SupplyRewardIndexes.GetRewardIndexIndex(coin.Denom) + if !foundUserRewardIndexIndex { + fmt.Printf("\n[LOG]: factor index for %s should always be found", coin.Denom) // TODO: remove before production + continue + } + for _, globalRewardIndex := range globalRewardIndexes { userRewardIndex, foundUserRewardIndex := userRewardIndexes.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) if !foundUserRewardIndex { continue } - userRewardIndexIndex, foundUserRewardIndexIndex := userRewardIndexes.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) - if !foundUserRewardIndexIndex { - fmt.Printf("\n[LOG]: factor index for %s should always be found", coin.Denom) // TODO: remove before production - continue - } globalRewardFactor := globalRewardIndex.RewardFactor userRewardFactor := userRewardIndex.RewardFactor @@ -377,16 +378,17 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo continue } + userRewardIndexIndex, foundUserRewardIndexIndex := claim.BorrowRewardIndexes.GetRewardIndexIndex(coin.Denom) + if !foundUserRewardIndexIndex { + fmt.Printf("\n[LOG]: factor index for %s should always be found", coin.Denom) // TODO: remove before production + continue + } + for _, globalRewardIndex := range globalRewardIndexes { userRewardIndex, foundUserRewardIndex := userRewardIndexes.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) if !foundUserRewardIndex { continue } - userRewardIndexIndex, foundUserRewardIndexIndex := userRewardIndexes.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) - if !foundUserRewardIndexIndex { - fmt.Printf("\n[LOG]: factor index for %s should always be found", coin.Denom) // TODO: remove before production - continue - } globalRewardFactor := globalRewardIndex.RewardFactor userRewardFactor := userRewardIndex.RewardFactor @@ -616,11 +618,11 @@ func (k Keeper) SynchronizeHardLiquidityProviderClaim(ctx sdk.Context, owner sdk k.SynchronizeHardSupplyReward(ctx, deposit) } - // // Synchronize any hard liquidity borrow-side rewards - // borrow, foundBorrow := k.hardKeeper.GetBorrow(ctx, owner) - // if foundBorrow { - // k.SynchronizeHardBorrowReward(ctx, borrow) - // } + // Synchronize any hard liquidity borrow-side rewards + borrow, foundBorrow := k.hardKeeper.GetBorrow(ctx, owner) + if foundBorrow { + k.SynchronizeHardBorrowReward(ctx, borrow) + } // Synchronize any hard delegator rewards k.SynchronizeHardDelegatorRewards(ctx, owner) @@ -668,16 +670,17 @@ func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiq continue } + userRewardIndexIndex, foundUserRewardIndexIndex := claim.SupplyRewardIndexes.GetRewardIndexIndex(ri.CollateralType) + if !foundUserRewardIndexIndex { + fmt.Printf("\n[LOG]: factor index for %s should always be found", ri.CollateralType) // TODO: remove before production + continue + } + for _, globalRewardIndex := range globalRewardIndexes { userRewardIndex, foundUserRewardIndex := userRewardIndexes.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) if !foundUserRewardIndex { continue } - userRewardIndexIndex, foundUserRewardIndexIndex := userRewardIndexes.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) - if !foundUserRewardIndexIndex { - fmt.Printf("\n[LOG]: factor index for %s should always be found", ri.CollateralType) // TODO: remove before production - continue - } globalRewardFactor := globalRewardIndex.RewardFactor userRewardFactor := userRewardIndex.RewardFactor @@ -717,16 +720,17 @@ func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiq continue } + userRewardIndexIndex, foundUserRewardIndexIndex := claim.BorrowRewardIndexes.GetRewardIndexIndex(ri.CollateralType) + if !foundUserRewardIndexIndex { + fmt.Printf("\n[LOG]: factor index for %s should always be found", ri.CollateralType) // TODO: remove before production + continue + } + for _, globalRewardIndex := range globalRewardIndexes { userRewardIndex, foundUserRewardIndex := userRewardIndexes.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType) if !foundUserRewardIndex { continue } - userRewardIndexIndex, foundUserRewardIndexIndex := userRewardIndexes.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType) - if !foundUserRewardIndexIndex { - fmt.Printf("\n[LOG]: factor index for %s should always be found", ri.CollateralType) // TODO: remove before production - continue - } globalRewardFactor := globalRewardIndex.RewardFactor userRewardFactor := userRewardIndex.RewardFactor @@ -748,7 +752,7 @@ func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiq fmt.Printf("[LOG]: factor index for %s should always be found", ri.CollateralType) // TODO: remove before production continue } - claim.SupplyRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor + claim.BorrowRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount) claim.Reward = claim.Reward.Add(newRewardsCoin) } diff --git a/x/incentive/keeper/rewards_test.go b/x/incentive/keeper/rewards_test.go index 4826dba9..71166a30 100644 --- a/x/incentive/keeper/rewards_test.go +++ b/x/incentive/keeper/rewards_test.go @@ -206,691 +206,354 @@ func (suite *KeeperTestSuite) TestSynchronizeUSDXMintingReward() { } } -// 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) - -// // 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.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(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) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom, defaultRewardIndexes) - -// // 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) - -// // 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.GetHardBorrowRewardPeriods(runCtx, tc.args.borrow.Denom) -// suite.Require().True(found) -// err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod) -// suite.Require().NoError(err) - -// rewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom) -// suite.Require().True(found) -// rewardFactor, found := rewardIndexes.GetRewardIndex(types.HardLiquidityRewardDenom) -// suite.Require().True(found) -// 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 -// } - -// 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), -// }, -// }, -// } -// 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.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(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) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom, defaultRewardIndexes) - -// // 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].RewardIndexes[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.GetHardBorrowRewardPeriods(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 -// rewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom) -// suite.Require().True(found) -// rewardFactor, found := rewardIndexes.GetRewardIndex(types.HardLiquidityRewardDenom) -// suite.Require().True(found) -// 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].RewardIndexes[0].RewardFactor) -// suite.Require().Equal(tc.args.expectedRewards, claim.Reward) -// }) -// } -// } - -// 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.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(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) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom, defaultRewardIndexes) - -// // 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.GetHardSupplyRewardPeriods(runCtx, tc.args.deposit.Denom) -// suite.Require().True(found) -// err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod) -// suite.Require().NoError(err) - -// rewardIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom) -// suite.Require().True(found) -// rewardFactor, found := rewardIndexes.GetRewardIndex(types.HardLiquidityRewardDenom) -// suite.Require().True(found) -// 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.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(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) -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom, defaultRewardIndexes) - -// // 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].RewardIndexes[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.GetHardSupplyRewardPeriods(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 -// rewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(suite.ctx, tc.args.deposit.Denom) -// suite.Require().True(found) -// rewardFactor, found := rewardIndexes.GetRewardIndex(types.HardLiquidityRewardDenom) -// suite.Require().True(found) -// 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].RewardIndexes[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 multiRewardPeriods types.MultiRewardPeriods -// 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) -// multiRewardPeriod := types.NewMultiRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond)) -// multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) -// } - -// // Setup incentive state -// params := types.NewParams( -// rewardPeriods, multiRewardPeriods, multiRewardPeriods, 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 -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// for _, denom := range tc.args.expectedSupplyIndexDenoms { -// suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, tc.args.initialTime) -// suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, denom, defaultRewardIndexes) -// } - -// // 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 multiRewardPeriods types.MultiRewardPeriods -// 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) -// multiRewardPeriod := types.NewMultiRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond)) -// multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) -// } - -// // Setup incentive state -// params := types.NewParams( -// rewardPeriods, multiRewardPeriods, multiRewardPeriods, 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) - -// defaultRewardIndexes := types.RewardIndexes{types.NewRewardIndex(types.HardLiquidityRewardDenom, sdk.ZeroDec())} -// // Set each initial deposit denom's previous accrual time and supply reward factor -// for _, coin := range tc.args.initialDeposit { -// suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime) -// suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, coin.Denom, defaultRewardIndexes) -// } - -// // 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.SetHardBorrowRewardIndexes(suite.ctx, denom, defaultRewardIndexes) -// } - -// // 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) TestAccumulateHardBorrowRewards() { + type args struct { + borrow sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + timeElapsed int + expectedRewardIndexes types.RewardIndexes + } + type test struct { + name string + args args + } + testCases := []test{ + { + "single reward denom: 7 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.000000856478000001"))}, + }, + }, + { + "single reward denom: 1 day", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.010571385600010177"))}, + }, + }, + { + "single reward denom: 0 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.0"))}, + }, + }, + { + "multiple reward denoms: 7 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.000000856478000001")), + types.NewRewardIndex("ukava", d("0.000000856478000001")), + }, + }, + }, + { + "multiple reward denoms: 1 day", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.010571385600010177")), + types.NewRewardIndex("ukava", d("0.010571385600010177")), + }, + }, + }, + { + "multiple reward denoms: 0 seconds", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.0")), + types.NewRewardIndex("ukava", d("0.0")), + }, + }, + }, + { + "multiple reward denoms with different rewards per second: 1 day", + args{ + borrow: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 555555)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.010571385600010177")), + types.NewRewardIndex("ukava", d("0.047999952000046210")), + }, + }, + }, + } + 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.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom, rewardIndexes) + + // 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) + + // 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) + + // Accumulate hard borrow rewards for the deposit denom + multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, tc.args.borrow.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardBorrowRewards(runCtx, multiRewardPeriod) + suite.Require().NoError(err) + + // Check that each expected reward index matches the current stored reward index for the denom + globalRewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(runCtx, tc.args.borrow.Denom) + suite.Require().True(found) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + globalRewardIndex, found := globalRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, globalRewardIndex) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSynchronizeHardBorrowReward() { + type args struct { + borrow sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + blockTimes []int + expectedRewardIndexes types.RewardIndexes + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "10 blocks", + args{ + borrow: c("bnb", 10000000000), // TODO: 2 decimal diff from TestAccumulateHardBorrowRewards's borrow + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.001223540000173228"))}, + expectedRewards: cs(c("hard", 12235400)), + }, + }, + { + "10 blocks - long block time", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("10.571385603126235340"))}, + expectedRewards: cs(c("hard", 105713856031)), + }, + }, + { + "multiple reward denoms: 10 blocks", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 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}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.001223540000173228")), + types.NewRewardIndex("ukava", d("0.001223540000173228")), + }, + expectedRewards: cs(c("hard", 12235400), c("ukava", 12235400)), + }, + }, + { + "multiple reward denoms: 10 blocks - long block time", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 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}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("10.571385603126235340")), + types.NewRewardIndex("ukava", d("10.571385603126235340")), + }, + expectedRewards: cs(c("hard", 105713856031), c("ukava", 105713856031)), + }, + }, + { + "multiple reward denoms with different rewards per second: 10 blocks", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 555555)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.001223540000173228")), + types.NewRewardIndex("ukava", d("0.005555550000786558")), + }, + expectedRewards: cs(c("hard", 12235400), c("ukava", 55555500)), + }, + }, + } + 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.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom, rewardIndexes) + + // 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) + multiRewardIndex, _ := claim.BorrowRewardIndexes.GetRewardIndex(tc.args.borrow.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.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) + + // Accumulate hard borrow-side rewards + multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom) + suite.Require().True(found) + err := suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod) + 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 the global reward index's reward factor and user's claim have been updated as expected + globalRewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom) + suite.Require().True(found) + claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + // Check that global reward index has been updated as expected + globalRewardIndex, found := globalRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, globalRewardIndex) + + // Check that the user's claim's reward index matches the corresponding global reward index + multiRewardIndex, found := claim.BorrowRewardIndexes.GetRewardIndex(tc.args.borrow.Denom) + suite.Require().True(found) + rewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, rewardIndex) + + // Check that the user's claim holds the expected amount of reward coins + suite.Require().Equal( + tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), + claim.Reward.AmountOf(expectedRewardIndex.CollateralType), + ) + } + }) + } +} func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() { type args struct { @@ -987,556 +650,1274 @@ func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() { } } -// func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() { -// type args struct { -// delegation sdk.Coin -// rewardsPerSecond sdk.Coin -// initialTime time.Time -// blockTimes []int -// expectedRewardFactor sdk.Dec -// expectedRewards sdk.Coin -// } -// type test struct { -// name string -// args args -// } +func (suite *KeeperTestSuite) TestAccumulateHardSupplyRewards() { + type args struct { + deposit sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + timeElapsed int + expectedRewardIndexes types.RewardIndexes + } + type test struct { + name string + args args + } + testCases := []test{ + { + "single reward denom: 7 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.000000856478000000"))}, + }, + }, + { + "single reward denom: 1 day", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.010571385600000000"))}, + }, + }, + { + "single reward denom: 0 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.0"))}, + }, + }, + { + "multiple reward denoms: 7 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 7, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.000000856478000000")), + types.NewRewardIndex("ukava", d("0.000000856478000000")), + }, + }, + }, + { + "multiple reward denoms: 1 day", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.010571385600000000")), + types.NewRewardIndex("ukava", d("0.010571385600000000")), + }, + }, + }, + { + "multiple reward denoms: 0 seconds", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 0, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.0")), + types.NewRewardIndex("ukava", d("0.0")), + }, + }, + }, + { + "multiple reward denoms with different rewards per second: 1 day", + args{ + deposit: c("bnb", 1000000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 555555)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + timeElapsed: 86400, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.010571385600000000")), + types.NewRewardIndex("ukava", d("0.047999952000000000")), + }, + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) -// testCases := []test{ -// { -// "10 blocks", -// args{ -// delegation: c("ukava", 1_000_000), -// 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("6.117700000000000000"), -// expectedRewards: c("hard", 6117700), -// }, -// }, -// { -// "10 blocks - long block time", -// args{ -// delegation: c("ukava", 1_000_000), -// 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("52856.928000000000000000"), -// expectedRewards: c("hard", 52856928000), -// }, -// }, -// } -// 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) -// // 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[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom, rewardIndexes) -// // setup incentive state -// params := types.NewParams( -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.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.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) -// suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.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) -// // Set up hard state (interest factor for the relevant denom) -// suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.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) -// // Delegator delegates -// err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) -// suite.Require().NoError(err) -// suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) -// 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) -// staking.EndBlocker(suite.ctx, suite.stakingKeeper) + // Run Hard begin blocker in order to update the denom's index factor + hard.BeginBlocker(runCtx, suite.hardKeeper) -// // Check that Staking hooks initialized a HardLiquidityProviderClaim -// claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor) + // Accumulate hard supply rewards for the deposit denom + multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, tc.args.deposit.Denom) + suite.Require().True(found) + err = suite.keeper.AccumulateHardSupplyRewards(runCtx, multiRewardPeriod) + suite.Require().NoError(err) -// // 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) + // Check that each expected reward index matches the current stored reward index for theh denom + globalRewardIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(runCtx, tc.args.deposit.Denom) + suite.Require().True(found) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + globalRewardIndex, found := globalRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, globalRewardIndex) + } + }) + } +} -// // Run Hard begin blocker for each block ctx to update denom's interest factor -// hard.BeginBlocker(blockCtx, suite.hardKeeper) +func (suite *KeeperTestSuite) TestSynchronizeHardSupplyReward() { + type args struct { + deposit sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + blockTimes []int + expectedRewardIndexes types.RewardIndexes + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } -// rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) -// suite.Require().True(found) + testCases := []test{ + { + "single reward denom: 10 blocks", + args{ + deposit: c("bnb", 10000000000), // TODO: 2 decimal diff + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.001223540000000000"))}, + expectedRewards: cs(c("hard", 12235400)), + }, + }, + { + "single reward denom: 10 blocks - long block time", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("10.571385600000000000"))}, + expectedRewards: cs(c("hard", 105713856000)), + }, + }, + { + "multiple reward denoms: 10 blocks", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 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}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.001223540000000000")), + types.NewRewardIndex("ukava", d("0.001223540000000000")), + }, + expectedRewards: cs(c("hard", 12235400), c("ukava", 12235400)), + }, + }, + { + "multiple reward denoms: 10 blocks - long block time", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 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}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("10.571385600000000000")), + types.NewRewardIndex("ukava", d("10.571385600000000000")), + }, + expectedRewards: cs(c("hard", 105713856000), c("ukava", 105713856000)), + }, + }, + { + "multiple reward denoms with different rewards per second: 10 blocks", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 555555)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + expectedRewardIndexes: types.RewardIndexes{ + types.NewRewardIndex("hard", d("0.001223540000000000")), + types.NewRewardIndex("ukava", d("0.005555550000000000")), + }, + expectedRewards: cs(c("hard", 12235400), c("ukava", 55555500)), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) -// err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) -// suite.Require().NoError(err) -// } -// updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) -// suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + // 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) -// // After we've accumulated, run synchronize -// suite.Require().NotPanics(func() { -// suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0]) -// }) + // 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[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom, rewardIndexes) -// // Check that reward factor and claim have been updated as expected -// rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom) -// suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) + // 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) -// claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(tc.args.expectedRewardFactor, claim.DelegatorRewardIndexes[0].RewardFactor) -// suite.Require().Equal(tc.args.expectedRewards, claim.Reward) -// }) -// } -// } + // 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) -// func (suite *KeeperTestSuite) TestSimulateHardSupplyRewardSynchronization() { -// 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 -// } + // Check that Hard hooks initialized a HardLiquidityProviderClaim with 0 reward indexes + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + multiRewardIndex, _ := claim.SupplyRewardIndexes.GetRewardIndex(tc.args.deposit.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.RewardFactor) + } -// testCases := []test{ -// { -// "10 blocks", -// args{ -// deposit: c("bnb", 10000000000), -// 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) + // 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) -// // 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) + // Run Hard begin blocker for each block ctx to update denom's interest factor + hard.BeginBlocker(blockCtx, suite.hardKeeper) -// // 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()) + // Accumulate hard supply-side rewards + multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom) + suite.Require().True(found) + err := suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) -// // 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) + // 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) + }) -// // 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 the global reward index's reward factor and user's claim have been updated as expected + globalRewardIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom) + suite.Require().True(found) + claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(found) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + // Check that global reward index has been updated as expected + globalRewardIndex, found := globalRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, globalRewardIndex) -// // 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) + // Check that the user's claim's reward index matches the corresponding global reward index + multiRewardIndex, found := claim.SupplyRewardIndexes.GetRewardIndex(tc.args.deposit.Denom) + suite.Require().True(found) + rewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, rewardIndex) -// // 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) + // Check that the user's claim holds the expected amount of reward coins + suite.Require().Equal( + tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), + claim.Reward.AmountOf(expectedRewardIndex.CollateralType), + ) + } + }) + } +} -// // Run Hard begin blocker for each block ctx to update denom's interest factor -// hard.BeginBlocker(blockCtx, suite.hardKeeper) +func (suite *KeeperTestSuite) TestUpdateHardSupplyIndexDenoms() { + type args struct { + firstDeposit sdk.Coins + secondDeposit sdk.Coins + rewardsPerSecond sdk.Coins + initialTime time.Time + expectedSupplyIndexDenoms []string + } + type test struct { + name string + args args + } -// rewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriod(blockCtx, tc.args.deposit.Denom) -// suite.Require().True(found) + testCases := []test{ + { + "single reward denom: update adds one supply reward index", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "single reward denom: update adds multiple supply reward indexes", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000), c("btcb", 10000000000), c("xrp", 10000000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava", "btcb", "xrp"}, + }, + }, + { + "single reward denom: update doesn't add duplicate supply reward index for same denom", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb"}, + }, + }, + { + "multiple reward denoms: update adds one supply reward index", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "multiple reward denoms: update adds multiple supply reward indexes", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("ukava", 10000000000), c("btcb", 10000000000), c("xrp", 10000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedSupplyIndexDenoms: []string{"bnb", "ukava", "btcb", "xrp"}, + }, + }, + { + "multiple reward denoms: update doesn't add duplicate supply reward index for same denom", + args{ + firstDeposit: cs(c("bnb", 10000000000)), + secondDeposit: cs(c("bnb", 5000000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 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) -// 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) + // 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) -// claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) -// suite.Require().True(found) -// suite.Require().Equal(claim.SupplyRewardIndexes[0].RewardFactor, sdk.ZeroDec()) -// suite.Require().Equal(claim.Reward, sdk.NewCoin("hard", sdk.ZeroInt())) + // Set up generic reward periods + var multiRewardPeriods types.MultiRewardPeriods + var rewardPeriods types.RewardPeriods + for i, denom := range tc.args.expectedSupplyIndexDenoms { + // Create just one reward period for USDX Minting / Hard Delegator reward periods (otherwise params will panic on duplicate) + if i == 0 { + rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[i]) + rewardPeriods = append(rewardPeriods, rewardPeriod) + } + multiRewardPeriod := types.NewMultiRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) + multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) + } -// updatedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) -// suite.Require().Equal(updatedClaim.SupplyRewardIndexes[0].RewardFactor, tc.args.expectedRewardFactor) -// suite.Require().Equal(updatedClaim.Reward, tc.args.expectedRewards) -// }) -// } -// } + // Setup incentive state + params := types.NewParams( + rewardPeriods, multiRewardPeriods, multiRewardPeriods, 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) -// func (suite *KeeperTestSuite) TestSimulateHardBorrowRewardSynchronization() { -// 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 -// } + // Set each denom's previous accrual time and supply reward factor + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + for _, denom := range tc.args.expectedSupplyIndexDenoms { + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, tc.args.initialTime) + suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, denom, rewardIndexes) + } -// testCases := []test{ -// { -// "10 blocks", -// args{ -// borrow: c("bnb", 10000000000), -// 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), -// }, -// }, -// } -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// suite.SetupWithGenState() -// suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + // 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) -// // 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) + // 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)) -// // 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()) + // User deposits (second time) + err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.secondDeposit) + suite.Require().NoError(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) + // 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)) + }) + } +} -// // 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) +func (suite *KeeperTestSuite) TestUpdateHardBorrowIndexDenoms() { + type args struct { + initialDeposit sdk.Coins + firstBorrow sdk.Coins + secondBorrow sdk.Coins + rewardsPerSecond sdk.Coins + initialTime time.Time + expectedBorrowIndexDenoms []string + } + type test struct { + name string + args args + } -// // 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) + testCases := []test{ + { + "single reward denom: update adds one borrow reward index", + args{ + initialDeposit: cs(c("bnb", 10000000000)), + firstBorrow: cs(c("bnb", 50000000)), + secondBorrow: cs(c("ukava", 500000000)), + rewardsPerSecond: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "single reward denom: 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: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"btcb", "ukava", "bnb", "xrp"}, + }, + }, + { + "single reward denom: 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: cs(c("hard", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"bnb"}, + }, + }, + { + "multiple reward denoms: update adds one borrow reward index", + args{ + initialDeposit: cs(c("bnb", 10000000000)), + firstBorrow: cs(c("bnb", 50000000)), + secondBorrow: cs(c("ukava", 500000000)), + rewardsPerSecond: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"bnb", "ukava"}, + }, + }, + { + "multiple reward denoms: 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: cs(c("hard", 122354), c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + expectedBorrowIndexDenoms: []string{"btcb", "ukava", "bnb", "xrp"}, + }, + }, + { + "multiple reward denoms: 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: cs(c("hard", 122354), c("ukava", 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) -// // 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) + // 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) -// // Run Hard begin blocker for each block ctx to update denom's interest factor -// hard.BeginBlocker(blockCtx, suite.hardKeeper) + // Set up generic reward periods + var multiRewardPeriods types.MultiRewardPeriods + var rewardPeriods types.RewardPeriods + for i, denom := range tc.args.expectedBorrowIndexDenoms { + // Create just one reward period for USDX Minting / Hard Delegator reward periods (otherwise params will panic on duplicate) + if i == 0 { + rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[i]) + rewardPeriods = append(rewardPeriods, rewardPeriod) + } + multiRewardPeriod := types.NewMultiRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond) + multiRewardPeriods = append(multiRewardPeriods, multiRewardPeriod) + } -// rewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriod(blockCtx, tc.args.borrow.Denom) -// suite.Require().True(found) + // Setup incentive state + params := types.NewParams( + rewardPeriods, multiRewardPeriods, multiRewardPeriods, 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) -// 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) + // Set each expected borrow denom's previous accrual time and borrow reward factor + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + for _, denom := range tc.args.expectedBorrowIndexDenoms { + suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, tc.args.initialTime) + suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, denom, rewardIndexes) + } -// claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) -// suite.Require().True(found) -// suite.Require().Equal(claim.BorrowRewardIndexes[0].RewardFactor, sdk.ZeroDec()) -// suite.Require().Equal(claim.Reward, sdk.NewCoin("hard", sdk.ZeroInt())) + // 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) -// updatedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) -// suite.Require().Equal(updatedClaim.BorrowRewardIndexes[0].RewardFactor, tc.args.expectedRewardFactor) -// suite.Require().Equal(updatedClaim.Reward, tc.args.expectedRewards) -// }) -// } -// } + // 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)) -// func (suite *KeeperTestSuite) TestSimulateHardDelegatorRewardSynchronization() { -// type args struct { -// delegation sdk.Coin -// rewardsPerSecond sdk.Coin -// initialTime time.Time -// blockTimes []int -// expectedRewardFactor sdk.Dec -// expectedRewards sdk.Coin -// } -// type test struct { -// name string -// args args -// } + // User borrows (first time) + err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.firstBorrow) + suite.Require().NoError(err) -// testCases := []test{ -// { -// "10 blocks", -// args{ -// delegation: c("ukava", 1_000_000), -// 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("6.117700000000000000"), -// expectedRewards: c("hard", 6117700), -// }, -// }, -// { -// "10 blocks - long block time", -// args{ -// delegation: c("ukava", 1_000_000), -// 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("52856.928000000000000000"), -// expectedRewards: c("hard", 52856928000), -// }, -// }, -// } + // 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)) -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// suite.SetupWithGenState() -// suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + // User borrows (second time) + err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.secondBorrow) + suite.Require().NoError(err) -// // 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) + // 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)) + }) + } +} -// // setup incentive state -// params := types.NewParams( -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, -// types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.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.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) -// suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) +func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() { + type args struct { + delegation sdk.Coin + rewardsPerSecond sdk.Coin + initialTime time.Time + blockTimes []int + expectedRewardFactor sdk.Dec + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } -// // Set up hard state (interest factor for the relevant denom) -// suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) + testCases := []test{ + { + "10 blocks", + args{ + delegation: c("ukava", 1_000_000), + 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("6.117700000000000000"), + expectedRewards: cs(c("hard", 6117700)), + }, + }, + { + "10 blocks - long block time", + args{ + delegation: c("ukava", 1_000_000), + 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("52856.928000000000000000"), + expectedRewards: cs(c("hard", 52856928000)), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) -// // Delegator delegates -// err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) -// suite.Require().NoError(err) -// suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) -// suite.Require().NoError(err) + // 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) -// staking.EndBlocker(suite.ctx, suite.stakingKeeper) + // setup incentive state + params := types.NewParams( + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), cs(tc.args.rewardsPerSecond))}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.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.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) + suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) -// // Check that Staking hooks initialized a HardLiquidityProviderClaim -// claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor) + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) -// // 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) + // Delegator delegates + err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) + suite.Require().NoError(err) + suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) + suite.Require().NoError(err) -// // Run Hard begin blocker for each block ctx to update denom's interest factor -// hard.BeginBlocker(blockCtx, suite.hardKeeper) + staking.EndBlocker(suite.ctx, suite.stakingKeeper) -// rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) -// suite.Require().True(found) + // Check that Staking hooks initialized a HardLiquidityProviderClaim + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor) -// err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) -// suite.Require().NoError(err) -// } -// updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) -// suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + // 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) -// claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(claim.DelegatorRewardIndexes[0].RewardFactor, sdk.ZeroDec()) -// suite.Require().Equal(claim.Reward, sdk.NewCoin("hard", sdk.ZeroInt())) + // Run Hard begin blocker for each block ctx to update denom's interest factor + hard.BeginBlocker(blockCtx, suite.hardKeeper) -// updatedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) -// suite.Require().Equal(updatedClaim.DelegatorRewardIndexes[0].RewardFactor, tc.args.expectedRewardFactor) -// suite.Require().Equal(updatedClaim.Reward, tc.args.expectedRewards) -// }) -// } -// } + rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) + suite.Require().True(found) -// func (suite *KeeperTestSuite) TestSimulateUSDXMintingRewardSynchronization() { -// type args struct { -// ctype string -// rewardsPerSecond sdk.Coin -// initialTime time.Time -// initialCollateral sdk.Coin -// initialPrincipal sdk.Coin -// blockTimes []int -// expectedRewardFactor sdk.Dec -// expectedRewards sdk.Coin -// } -// type test struct { -// name string -// args args -// } + err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) -// testCases := []test{ -// { -// "10 blocks", -// args{ -// ctype: "bnb-a", -// rewardsPerSecond: c("ukava", 122354), -// initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), -// initialCollateral: c("bnb", 1000000000000), -// initialPrincipal: c("usdx", 10000000000), -// blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, -// expectedRewardFactor: d("0.001223540000000000"), -// expectedRewards: c("ukava", 12235400), -// }, -// }, -// { -// "10 blocks - long block time", -// args{ -// ctype: "bnb-a", -// rewardsPerSecond: c("ukava", 122354), -// initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), -// initialCollateral: c("bnb", 1000000000000), -// initialPrincipal: c("usdx", 10000000000), -// blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, -// expectedRewardFactor: d("10.57138560000000000"), -// expectedRewards: c("ukava", 105713856000), -// }, -// }, -// } -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// suite.SetupWithGenState() -// suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) + // After we've accumulated, run synchronize + suite.Require().NotPanics(func() { + suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0]) + }) -// // 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.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) -// suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) + // Check that reward factor and claim have been updated as expected + rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom) + suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) -// // setup account state -// sk := suite.app.GetSupplyKeeper() -// sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral)) -// sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral)) + claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(tc.args.expectedRewardFactor, claim.DelegatorRewardIndexes[0].RewardFactor) + suite.Require().Equal(tc.args.expectedRewards, claim.Reward) + }) + } +} -// // setup cdp state -// cdpKeeper := suite.app.GetCDPKeeper() -// err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) -// suite.Require().NoError(err) +func (suite *KeeperTestSuite) TestSimulateHardSupplyRewardSynchronization() { + type args struct { + deposit sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + blockTimes []int + expectedRewardIndexes types.RewardIndexes + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } -// claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) + testCases := []test{ + { + "10 blocks", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.001223540000000000"))}, + expectedRewards: cs(c("hard", 12235400)), + }, + }, + { + "10 blocks - long block time", + args{ + deposit: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("10.571385600000000000"))}, + expectedRewards: cs(c("hard", 105713856000)), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWithGenState() + suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) -// 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) -// rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype) -// suite.Require().True(found) -// err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod) -// suite.Require().NoError(err) -// } -// updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) -// suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + // 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) -// claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) -// suite.Require().True(found) -// suite.Require().Equal(claim.RewardIndexes[0].RewardFactor, sdk.ZeroDec()) -// suite.Require().Equal(claim.Reward, sdk.NewCoin("ukava", sdk.ZeroInt())) + // 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[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, tc.args.deposit.Denom, rewardIndexes) -// updatedClaim := suite.keeper.SimulateUSDXMintingSynchronization(suite.ctx, claim) -// suite.Require().Equal(tc.args.expectedRewardFactor, updatedClaim.RewardIndexes[0].RewardFactor) -// suite.Require().Equal(tc.args.expectedRewards, updatedClaim.Reward) -// }) -// } -// } + // 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 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) + multiRewardIndex, _ := claim.SupplyRewardIndexes.GetRewardIndex(tc.args.deposit.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.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) + + // Accumulate hard supply-side rewards + multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom) + suite.Require().True(found) + err := suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + // Confirm that the user's claim hasn't been synced + claimPre, foundPre := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(foundPre) + multiRewardIndexPre, _ := claimPre.SupplyRewardIndexes.GetRewardIndex(tc.args.deposit.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndexPre.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.RewardFactor) + } + + // Check that the synced claim held in memory has properly simulated syncing + syncedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claimPre) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + // Check that the user's claim's reward index matches the expected reward index + multiRewardIndex, found := syncedClaim.SupplyRewardIndexes.GetRewardIndex(tc.args.deposit.Denom) + suite.Require().True(found) + rewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, rewardIndex) + + // Check that the user's claim holds the expected amount of reward coins + suite.Require().Equal( + tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), + syncedClaim.Reward.AmountOf(expectedRewardIndex.CollateralType), + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSimulateHardBorrowRewardSynchronization() { + type args struct { + borrow sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + blockTimes []int + expectedRewardIndexes types.RewardIndexes + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "10 blocks", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("0.001223540000173228"))}, + expectedRewards: cs(c("hard", 12235400)), + }, + }, + { + "10 blocks - long block time", + args{ + borrow: c("bnb", 10000000000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("hard", d("10.571385603126235340"))}, + expectedRewards: cs(c("hard", 105713856031)), + }, + }, + } + 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.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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) + var rewardIndexes types.RewardIndexes + for _, rewardCoin := range tc.args.rewardsPerSecond { + rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec()) + rewardIndexes = append(rewardIndexes, rewardIndex) + } + suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, tc.args.borrow.Denom, rewardIndexes) + + // 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) + multiRewardIndex, _ := claim.BorrowRewardIndexes.GetRewardIndex(tc.args.borrow.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.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) + + // Accumulate hard borrow-side rewards + multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom) + suite.Require().True(found) + err := suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + // Confirm that the user's claim hasn't been synced + claimPre, foundPre := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3]) + suite.Require().True(foundPre) + multiRewardIndexPre, _ := claimPre.BorrowRewardIndexes.GetRewardIndex(tc.args.borrow.Denom) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + currRewardIndex, found := multiRewardIndexPre.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), currRewardIndex.RewardFactor) + } + + // Check that the synced claim held in memory has properly simulated syncing + syncedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + // Check that the user's claim's reward index matches the expected reward index + multiRewardIndex, found := syncedClaim.BorrowRewardIndexes.GetRewardIndex(tc.args.borrow.Denom) + suite.Require().True(found) + rewardIndex, found := multiRewardIndex.RewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, rewardIndex) + + // Check that the user's claim holds the expected amount of reward coins + suite.Require().Equal( + tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), + syncedClaim.Reward.AmountOf(expectedRewardIndex.CollateralType), + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSimulateHardDelegatorRewardSynchronization() { + type args struct { + delegation sdk.Coin + rewardsPerSecond sdk.Coins + initialTime time.Time + blockTimes []int + expectedRewardIndexes types.RewardIndexes + expectedRewards sdk.Coins + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "10 blocks", + args{ + delegation: c("ukava", 1_000_000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("ukava", d("6.117700000000000000"))}, // Here the reward index stores data differently than inside a MultiRewardIndex + expectedRewards: cs(c("hard", 6117700)), + }, + }, + { + "10 blocks - long block time", + args{ + delegation: c("ukava", 1_000_000), + rewardsPerSecond: cs(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}, + expectedRewardIndexes: types.RewardIndexes{types.NewRewardIndex("ukava", d("52856.928000000000000000"))}, + expectedRewards: cs(c("hard", 52856928000)), + }, + }, + } + + 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.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond[0])}, + 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.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) + suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec()) + + // Set up hard state (interest factor for the relevant denom) + suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime) + + // Delegator delegates + err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation) + suite.Require().NoError(err) + suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation) + suite.Require().NoError(err) + + staking.EndBlocker(suite.ctx, suite.stakingKeeper) + + // Check that Staking hooks initialized a HardLiquidityProviderClaim + claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[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) + + // Accumulate hard delegator rewards + rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom) + suite.Require().True(found) + err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + // Check that the synced claim held in memory has properly simulated syncing + syncedClaim := suite.keeper.SimulateHardSynchronization(suite.ctx, claim) + for _, expectedRewardIndex := range tc.args.expectedRewardIndexes { + // Check that the user's claim's reward index matches the expected reward index + rewardIndex, found := syncedClaim.DelegatorRewardIndexes.GetRewardIndex(expectedRewardIndex.CollateralType) + suite.Require().True(found) + suite.Require().Equal(expectedRewardIndex, rewardIndex) + + // Check that the user's claim holds the expected amount of reward coins + suite.Require().Equal( + tc.args.expectedRewards.AmountOf(expectedRewardIndex.CollateralType), + syncedClaim.Reward.AmountOf(expectedRewardIndex.CollateralType), + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSimulateUSDXMintingRewardSynchronization() { + type args struct { + ctype string + rewardsPerSecond sdk.Coins + initialTime time.Time + initialCollateral sdk.Coin + initialPrincipal sdk.Coin + blockTimes []int + expectedRewardFactor sdk.Dec + expectedRewards sdk.Coin + } + type test struct { + name string + args args + } + + testCases := []test{ + { + "10 blocks", + args{ + ctype: "bnb-a", + rewardsPerSecond: cs(c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + initialCollateral: c("bnb", 1000000000000), + initialPrincipal: c("usdx", 10000000000), + blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + expectedRewardFactor: d("0.001223540000000000"), + expectedRewards: c("ukava", 12235400), + }, + }, + { + "10 blocks - long block time", + args{ + ctype: "bnb-a", + rewardsPerSecond: cs(c("ukava", 122354)), + initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + initialCollateral: c("bnb", 1000000000000), + initialPrincipal: c("usdx", 10000000000), + blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400}, + expectedRewardFactor: d("10.57138560000000000"), + expectedRewards: c("ukava", 105713856000), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + 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[0])}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, + types.MultiRewardPeriods{types.NewMultiRewardPeriod(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[0])}, + 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.SetParams(suite.ctx, params) + 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() + sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral)) + sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral)) + + // setup cdp state + cdpKeeper := suite.app.GetCDPKeeper() + 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.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) + + 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) + rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype) + suite.Require().True(found) + err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod) + suite.Require().NoError(err) + } + updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed)) + suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) + + claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(claim.RewardIndexes[0].RewardFactor, sdk.ZeroDec()) + suite.Require().Equal(claim.Reward, sdk.NewCoin("ukava", sdk.ZeroInt())) + + updatedClaim := suite.keeper.SimulateUSDXMintingSynchronization(suite.ctx, claim) + suite.Require().Equal(tc.args.expectedRewardFactor, updatedClaim.RewardIndexes[0].RewardFactor) + suite.Require().Equal(tc.args.expectedRewards, updatedClaim.Reward) + }) + } +} func (suite *KeeperTestSuite) SetupWithGenState() { config := sdk.GetConfig() diff --git a/x/incentive/types/claims.go b/x/incentive/types/claims.go index 648d7d19..06358f00 100644 --- a/x/incentive/types/claims.go +++ b/x/incentive/types/claims.go @@ -458,6 +458,16 @@ func (mris MultiRewardIndexes) GetRewardIndex(denom string) (MultiRewardIndex, b return MultiRewardIndex{}, false } +// GetRewardIndexIndex fetches a specific reward index inside the array by its denom +func (mris MultiRewardIndexes) GetRewardIndexIndex(denom string) (int, bool) { + for i, ri := range mris { + if ri.CollateralType == denom { + return i, true + } + } + return -1, false +} + // Validate validation for reward indexes func (mris MultiRewardIndexes) Validate() error { for _, mri := range mris {