From 651de460cae2eb69bd433e166936d249a95a5857 Mon Sep 17 00:00:00 2001 From: Derrick Lee Date: Fri, 23 Sep 2022 09:38:22 -0700 Subject: [PATCH] Add weighted bkava support for earn incentives (#1299) * Add bkava handler for earn incentives * Add bkava accum tests * Add bkava denoms in index state * Set storeTimeEquals to default value * Add supply expected keepers * Add tests for proportional adjustment * Add liquid keeper to incentive keeper * Use weighted reward periods for bkava * Add liquid keeper to tests * Add Accumulate override rewards period with deccoins * Adjust test to handle sub unit coins * Add liquid keeper to test * Fix div by zero for proportional rewards * Update test for actual expected values * Update expected indexes to be same for different vaults * Allow no stored time for vaults that have no indexes or state * Add test for partial bkava deposit * Add math check to test * Deterministically iterate over bkava denoms * Remove unused expected liquid method GetAllDerivativeDenoms --- app/app.go | 2 + x/incentive/keeper/claim_test.go | 4 +- x/incentive/keeper/integration_test.go | 5 + x/incentive/keeper/keeper.go | 4 +- .../keeper/rewards_borrow_accum_test.go | 14 +- .../keeper/rewards_delegator_accum_test.go | 14 +- .../keeper/rewards_delegator_init_test.go | 2 +- .../keeper/rewards_delegator_sync_test.go | 16 +- x/incentive/keeper/rewards_earn.go | 126 ++++- x/incentive/keeper/rewards_earn_accum_test.go | 474 +++++++++++++++++- .../keeper/rewards_earn_proportional_test.go | 86 ++++ x/incentive/keeper/rewards_earn_sync_test.go | 6 +- .../keeper/rewards_supply_accum_test.go | 14 +- x/incentive/keeper/rewards_swap_accum_test.go | 14 +- x/incentive/keeper/rewards_swap_sync_test.go | 6 +- x/incentive/keeper/rewards_usdx_accum_test.go | 12 +- x/incentive/keeper/unit_test.go | 72 ++- x/incentive/types/accumulator.go | 29 +- x/incentive/types/accumulator_test.go | 6 +- x/incentive/types/expected_keepers.go | 8 + 20 files changed, 837 insertions(+), 77 deletions(-) create mode 100644 x/incentive/keeper/rewards_earn_proportional_test.go diff --git a/app/app.go b/app/app.go index 3843469b..df12fd43 100644 --- a/app/app.go +++ b/app/app.go @@ -619,6 +619,8 @@ func NewApp( app.stakingKeeper, &swapKeeper, &savingsKeeper, + // TODO: Liquid keeper + nil, &earnKeeper, ) diff --git a/x/incentive/keeper/claim_test.go b/x/incentive/keeper/claim_test.go index e215eeae..4ca39a96 100644 --- a/x/incentive/keeper/claim_test.go +++ b/x/incentive/keeper/claim_test.go @@ -36,7 +36,7 @@ func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() { }, }, } - suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -70,7 +70,7 @@ func (suite *ClaimTests) TestCannotClaimAfterEndTime() { ClaimEnd: endTime, }, } - suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil, nil, nil) suite.ctx = suite.ctx.WithBlockTime(endTime.Add(time.Nanosecond)) diff --git a/x/incentive/keeper/integration_test.go b/x/incentive/keeper/integration_test.go index 655fa44d..4f4a173e 100644 --- a/x/incentive/keeper/integration_test.go +++ b/x/incentive/keeper/integration_test.go @@ -18,7 +18,12 @@ import ( func i(in int64) sdk.Int { return sdk.NewInt(in) } func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func dc(denom string, amount string) sdk.DecCoin { + return sdk.NewDecCoinFromDec(denom, sdk.MustNewDecFromStr(amount)) +} func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } +func toDcs(coins ...sdk.Coin) sdk.DecCoins { return sdk.NewDecCoinsFromCoins(coins...) } +func dcs(coins ...sdk.DecCoin) sdk.DecCoins { return sdk.NewDecCoins(coins...) } func NewCDPGenStateMulti(cdc codec.JSONCodec) app.GenesisState { cdpGenesis := cdptypes.GenesisState{ diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index bce59550..15b5475a 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -22,6 +22,7 @@ type Keeper struct { stakingKeeper types.StakingKeeper swapKeeper types.SwapKeeper savingsKeeper types.SavingsKeeper + liquidKeeper types.LiquidKeeper earnKeeper types.EarnKeeper } @@ -29,7 +30,7 @@ type Keeper struct { func NewKeeper( cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, - swpk types.SwapKeeper, svk types.SavingsKeeper, ek types.EarnKeeper, + swpk types.SwapKeeper, svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper, ) Keeper { if !paramstore.HasKeyTable() { paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) @@ -46,6 +47,7 @@ func NewKeeper( stakingKeeper: stk, swapKeeper: swpk, savingsKeeper: svk, + liquidKeeper: lqk, earnKeeper: ek, } } diff --git a/x/incentive/keeper/rewards_borrow_accum_test.go b/x/incentive/keeper/rewards_borrow_accum_test.go index 2fd39985..e4c7fc35 100644 --- a/x/incentive/keeper/rewards_borrow_accum_test.go +++ b/x/incentive/keeper/rewards_borrow_accum_test.go @@ -39,7 +39,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateUpdatedWhenBlockTimeHasIncre denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) suite.storeGlobalBorrowIndexes(types.MultiRewardIndexes{ { @@ -91,7 +91,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateUnchangedWhenBlockTimeHasNot denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -136,7 +136,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenSourceSharesAre denom := "bnb" hardKeeper := newFakeHardKeeper() // zero total borrows - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -182,7 +182,7 @@ func (suite *AccumulateBorrowRewardsTests) TestStateAddedWhenStateDoesNotExist() denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -225,7 +225,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoPanicWhenStateDoesNotExist() { denom := "bnb" hardKeeper := newFakeHardKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -253,7 +253,7 @@ func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenBeforeStartTime denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -299,7 +299,7 @@ func (suite *AccumulateBorrowRewardsTests) TestPanicWhenCurrentTimeLessThanPrevi denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_delegator_accum_test.go b/x/incentive/keeper/rewards_delegator_accum_test.go index a7537fc2..883c377b 100644 --- a/x/incentive/keeper/rewards_delegator_accum_test.go +++ b/x/incentive/keeper/rewards_delegator_accum_test.go @@ -36,7 +36,7 @@ func TestAccumulateDelegatorRewards(t *testing.T) { func (suite *AccumulateDelegatorRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) suite.storeGlobalDelegatorIndexes(types.MultiRewardIndexes{ { @@ -86,7 +86,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateUpdatedWhenBlockTimeHasIn func (suite *AccumulateDelegatorRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -129,7 +129,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateUnchangedWhenBlockTimeHas func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() { stakingKeeper := newFakeStakingKeeper() // zero total bonded - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -173,7 +173,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenSourceShares func (suite *AccumulateDelegatorRewardsTests) TestStateAddedWhenStateDoesNotExist() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -214,7 +214,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestStateAddedWhenStateDoesNotExis func (suite *AccumulateDelegatorRewardsTests) TestNoPanicWhenStateDoesNotExist() { stakingKeeper := newFakeStakingKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -240,7 +240,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoPanicWhenStateDoesNotExist() func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenBeforeStartTime() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -284,7 +284,7 @@ func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenBeforeStartT func (suite *AccumulateDelegatorRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() { stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_delegator_init_test.go b/x/incentive/keeper/rewards_delegator_init_test.go index 8d1eda3f..42015e43 100644 --- a/x/incentive/keeper/rewards_delegator_init_test.go +++ b/x/incentive/keeper/rewards_delegator_init_test.go @@ -58,7 +58,7 @@ func (suite *InitializeDelegatorRewardTests) TestClaimIsSyncedAndIndexesAreSetWh DelegatorShares: d("1000"), }}, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, sk, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, sk, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ diff --git a/x/incentive/keeper/rewards_delegator_sync_test.go b/x/incentive/keeper/rewards_delegator_sync_test.go index f6f60f95..7666eba2 100644 --- a/x/incentive/keeper/rewards_delegator_sync_test.go +++ b/x/incentive/keeper/rewards_delegator_sync_test.go @@ -37,7 +37,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGl delegator := arbitraryAddress() stakingKeeper := &fakeStakingKeeper{} // use an empty staking keeper that returns no delegations - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -58,7 +58,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGl func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlobalFactorIncreased() { delegator := arbitraryAddress() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, &fakeStakingKeeper{}, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, &fakeStakingKeeper{}, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -97,7 +97,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFac unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -142,7 +142,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewReward unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -192,7 +192,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFac unslashedBondedValidator(validatorAddress), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) claim := types.DelegatorClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -299,7 +299,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenValAddrIsNil() unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) suite.Equal( d("11"), // delegation to bonded validators @@ -343,7 +343,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenExcludingAVali unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) suite.Equal( d("10"), @@ -387,7 +387,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenIncludingAVali unslashedNotBondedValidator(validatorAddresses[3]), }, } - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil, nil, nil, nil) suite.Equal( d("111"), diff --git a/x/incentive/keeper/rewards_earn.go b/x/incentive/keeper/rewards_earn.go index 0e907317..09d2c5f6 100644 --- a/x/incentive/keeper/rewards_earn.go +++ b/x/incentive/keeper/rewards_earn.go @@ -2,6 +2,9 @@ package keeper import ( "fmt" + "sort" + "strings" + "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,26 +15,137 @@ import ( // AccumulateEarnRewards calculates new rewards to distribute this block and updates the global indexes to reflect this. // The provided rewardPeriod must be valid to avoid panics in calculating time durations. func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) { - previousAccrualTime, found := k.GetEarnRewardAccrualTime(ctx, rewardPeriod.CollateralType) + if rewardPeriod.CollateralType == "bkava" { + k.accumulateEarnBkavaRewards(ctx, rewardPeriod) + return + } + + k.accumulateEarnRewards( + ctx, + rewardPeriod.CollateralType, + rewardPeriod.Start, + rewardPeriod.End, + sdk.NewDecCoinsFromCoins(rewardPeriod.RewardsPerSecond...), + ) +} + +func GetProportionalRewardsPerSecond( + rewardPeriod types.MultiRewardPeriod, + totalBkavaSupply sdk.Int, + singleBkavaSupply sdk.Int, +) sdk.DecCoins { + // Rate per bkava-xxx = rewardsPerSecond * % of bkava-xxx + // = rewardsPerSecond * (bkava-xxx / total bkava) + // = (rewardsPerSecond * bkava-xxx) / total bkava + + newRate := sdk.NewDecCoins() + + // Prevent division by zero, if there are no total shares then there are no + // rewards. + if totalBkavaSupply.IsZero() { + return newRate + } + + for _, rewardCoin := range rewardPeriod.RewardsPerSecond { + scaledAmount := rewardCoin.Amount.ToDec(). + Mul(singleBkavaSupply.ToDec()). + Quo(totalBkavaSupply.ToDec()) + + newRate = newRate.Add(sdk.NewDecCoinFromDec(rewardCoin.Denom, scaledAmount)) + } + + return newRate +} + +// accumulateEarnBkavaRewards does the same as AccumulateEarnRewards but for +// *all* bkava vaults. +func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) { + // TODO: Get staking rewards and distribute + + // All bkava vault denoms + bkavaVaultsDenoms := make(map[string]bool) + + // bkava vault denoms from earn records (non-empty vaults) + k.earnKeeper.IterateVaultRecords(ctx, func(record earntypes.VaultRecord) (stop bool) { + // TODO: Replace with single bkava denom check method from liquid + if strings.HasPrefix(record.TotalShares.Denom, "bkava-") { + bkavaVaultsDenoms[record.TotalShares.Denom] = true + } + + return false + }) + + // bkava vault denoms from past incentive indexes, may include vaults + // that were fully withdrawn. + k.IterateEarnRewardIndexes(ctx, func(vaultDenom string, indexes types.RewardIndexes) (stop bool) { + if strings.HasPrefix(vaultDenom, "bkava-") { + bkavaVaultsDenoms[vaultDenom] = true + } + + return false + }) + + totalBkavaSupply := k.liquidKeeper.GetTotalDerivativeSupply(ctx) + + i := 0 + sortedBkavaVaultsDenoms := make([]string, len(bkavaVaultsDenoms)) + for vaultDenom := range bkavaVaultsDenoms { + sortedBkavaVaultsDenoms[i] = vaultDenom + i++ + } + + // Sort the vault denoms to ensure deterministic iteration order. + sort.Strings(sortedBkavaVaultsDenoms) + + // Accumulate rewards for each bkava vault. + for _, bkavaDenom := range sortedBkavaVaultsDenoms { + k.accumulateEarnRewards( + ctx, + bkavaDenom, + rewardPeriod.Start, + rewardPeriod.End, + GetProportionalRewardsPerSecond( + rewardPeriod, + totalBkavaSupply, + k.liquidKeeper.GetDerivativeSupply(ctx, bkavaDenom), + ), + ) + } +} + +func (k Keeper) accumulateEarnRewards( + ctx sdk.Context, + collateralType string, + periodStart time.Time, + periodEnd time.Time, + periodRewardsPerSecond sdk.DecCoins, +) { + previousAccrualTime, found := k.GetEarnRewardAccrualTime(ctx, collateralType) if !found { previousAccrualTime = ctx.BlockTime() } - indexes, found := k.GetEarnRewardIndexes(ctx, rewardPeriod.CollateralType) + indexes, found := k.GetEarnRewardIndexes(ctx, collateralType) if !found { indexes = types.RewardIndexes{} } acc := types.NewAccumulator(previousAccrualTime, indexes) - totalSource := k.getEarnTotalSourceShares(ctx, rewardPeriod.CollateralType) + totalSourceShares := k.getEarnTotalSourceShares(ctx, collateralType) - acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime()) + acc.AccumulateDecCoins( + periodStart, + periodEnd, + periodRewardsPerSecond, + totalSourceShares, + ctx.BlockTime(), + ) - k.SetEarnRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime) + k.SetEarnRewardAccrualTime(ctx, collateralType, acc.PreviousAccumulationTime) if len(acc.Indexes) > 0 { // the store panics when setting empty or nil indexes - k.SetEarnRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes) + k.SetEarnRewardIndexes(ctx, collateralType, acc.Indexes) } } diff --git a/x/incentive/keeper/rewards_earn_accum_test.go b/x/incentive/keeper/rewards_earn_accum_test.go index d2d931e5..42692fff 100644 --- a/x/incentive/keeper/rewards_earn_accum_test.go +++ b/x/incentive/keeper/rewards_earn_accum_test.go @@ -16,8 +16,12 @@ type AccumulateEarnRewardsTests struct { func (suite *AccumulateEarnRewardsTests) storedTimeEquals(vaultDenom string, expected time.Time) { storedTime, found := suite.keeper.GetEarnRewardAccrualTime(suite.ctx, vaultDenom) - suite.True(found) - suite.Equal(expected, storedTime) + suite.Equal(found, expected != time.Time{}, "expected time is %v but time found = %v", expected, found) + if found { + suite.Equal(expected, storedTime) + } else { + suite.Empty(storedTime) + } } func (suite *AccumulateEarnRewardsTests) storedIndexesEqual(vaultDenom string, expected types.RewardIndexes) { @@ -38,7 +42,7 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper().addVault(vaultDenom, earntypes.NewVaultShare(vaultDenom, d("1000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) suite.storeGlobalEarnIndexes(types.MultiRewardIndexes{ { @@ -86,11 +90,222 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas }) } +func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased_bkava() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + earnKeeper := newFakeEarnKeeper(). + addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("800000"))). + addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("200000"))) + + liquidKeeper := newFakeLiquidKeeper(). + addDerivative(vaultDenom1, i(800000)). + addDerivative(vaultDenom2, i(200000)) + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: vaultDenom1, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + { + CollateralType: vaultDenom2, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + } + + suite.storeGlobalEarnIndexes(globalIndexes) + previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime) + + newAccrualTime := previousAccrualTime.Add(1 * time.Hour) + suite.ctx = suite.ctx.WithBlockTime(newAccrualTime) + + rewardPeriod := types.NewMultiRewardPeriod( + true, + "bkava", // reward period is set for "bkava" to apply to all vaults + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(c("earn", 2000), c("ukava", 1000)), // same denoms as in global indexes + ) + suite.keeper.AccumulateEarnRewards(suite.ctx, rewardPeriod) + + // check time and factors + + suite.storedTimeEquals(vaultDenom1, newAccrualTime) + suite.storedTimeEquals(vaultDenom2, newAccrualTime) + + // Each vault gets the same ukava per second, assuming shares prices are the same. + // The share amount determines how much is actually distributed to the vault. + expectedIndexes := types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("7.22"), + }, + { + CollateralType: "ukava", + RewardFactor: d("3.64"), + }, + } + + suite.storedIndexesEqual(vaultDenom1, expectedIndexes) + suite.storedIndexesEqual(vaultDenom2, expectedIndexes) +} + +func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased_bkava_partialDeposit() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + vaultDenom1Supply := i(800000) + vaultDenom2Supply := i(200000) + + liquidKeeper := newFakeLiquidKeeper(). + addDerivative(vaultDenom1, vaultDenom1Supply). + addDerivative(vaultDenom2, vaultDenom2Supply) + + vault2Shares := d("100000") + + // More bkava minted than deposited into earn + // Rewards are higher per-share as a result + earnKeeper := newFakeEarnKeeper(). + addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("700000"))). + addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, vault2Shares)) + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: vaultDenom1, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + { + CollateralType: vaultDenom2, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + } + + suite.storeGlobalEarnIndexes(globalIndexes) + previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime) + + newAccrualTime := previousAccrualTime.Add(1 * time.Hour) + suite.ctx = suite.ctx.WithBlockTime(newAccrualTime) + + rewardPeriod := types.NewMultiRewardPeriod( + true, + "bkava", // reward period is set for "bkava" to apply to all vaults + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(c("earn", 2000), c("ukava", 1000)), // same denoms as in global indexes + ) + suite.keeper.AccumulateEarnRewards(suite.ctx, rewardPeriod) + + // check time and factors + + suite.storedTimeEquals(vaultDenom1, newAccrualTime) + suite.storedTimeEquals(vaultDenom2, newAccrualTime) + + // Slightly increased rewards due to less bkava deposited + suite.storedIndexesEqual(vaultDenom1, types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("8.248571428571428571"), + }, + { + CollateralType: "ukava", + RewardFactor: d("4.154285714285714286"), + }, + }) + + // Much higher rewards per share because only a small amount of bkava is + // deposited. The **total** amount of incentives distributed to this vault + // is still the same proportional amount. + + // Fixed amount total rewards distributed to the vault + // Fewer shares deposited -> higher rewards per share + + // 7.2ukava shares per second for 1 hour (started with 0.04) + // total rewards claimable = 7.2 * 100000 shares = 720000 ukava + + // 720000ukava distributed which is 20% of total bkava ukava rewards + // total rewards for *all* bkava vaults for 1 hour + // = 1000ukava per second * 3600 == 3600000ukava + // vaultDenom2 has 20% of the total bkava amount so it should get 20% of 3600000ukava == 720000ukava + + vault2expectedIndexes := types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("14.42"), + }, + { + CollateralType: "ukava", + RewardFactor: d("7.24"), + }, + } + suite.storedIndexesEqual(vaultDenom2, vault2expectedIndexes) + + // Verify math described above + totalVault2DistributedUkava := i(int64(time.Hour.Seconds())). + ToDec(). + Mul(rewardPeriod.RewardsPerSecond.AmountOf("ukava").ToDec()). + // 20% of total rewards + // vault 2 supply / (vault 1 supply + vault 2 supply) + Mul( + vaultDenom2Supply.ToDec(). + Quo(vaultDenom1Supply.Add(vaultDenom2Supply).ToDec()), + ) + + totalVault2ClaimableRewards := vault2expectedIndexes[1]. + RewardFactor.Sub(d("0.04")). // Rewards per share for 1 hr, excluding the starting value + Mul(vault2Shares) // * Shares in vault to get total rewards for entire vault + + suite.Equal(totalVault2DistributedUkava, totalVault2ClaimableRewards) +} + func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() { vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper().addVault(vaultDenom, earntypes.NewVaultShare(vaultDenom, d("1000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) previousIndexes := types.MultiRewardIndexes{ { @@ -131,11 +346,86 @@ func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn suite.storedIndexesEqual(vaultDenom, expected) } +func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased_bkava() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + earnKeeper := newFakeEarnKeeper(). + addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))). + addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000"))) + + liquidKeeper := newFakeLiquidKeeper(). + addDerivative(vaultDenom1, i(1000000)). + addDerivative(vaultDenom2, i(1000000)) + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + previousIndexes := types.MultiRewardIndexes{ + { + CollateralType: vaultDenom1, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + { + CollateralType: vaultDenom2, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + } + suite.storeGlobalEarnIndexes(previousIndexes) + previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime) + + suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime) + + period := types.NewMultiRewardPeriod( + true, + "bkava", + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(c("earn", 2000), c("ukava", 1000)), // same denoms as in global indexes + ) + + suite.keeper.AccumulateEarnRewards(suite.ctx, period) + + // check time and factors + + suite.storedTimeEquals(vaultDenom1, previousAccrualTime) + suite.storedTimeEquals(vaultDenom2, previousAccrualTime) + + expected, f := previousIndexes.Get(vaultDenom1) + suite.True(f) + suite.storedIndexesEqual(vaultDenom1, expected) + + expected, f = previousIndexes.Get(vaultDenom2) + suite.True(f) + suite.storedIndexesEqual(vaultDenom2, expected) +} + func (suite *AccumulateEarnRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() { vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper() // no vault, so no source shares - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + liquidKeeper := newFakeLiquidKeeper() + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) previousIndexes := types.MultiRewardIndexes{ { @@ -177,11 +467,82 @@ func (suite *AccumulateEarnRewardsTests) TestNoAccumulationWhenSourceSharesAreZe suite.storedIndexesEqual(vaultDenom, expected) } +func (suite *AccumulateEarnRewardsTests) TestNoAccumulationWhenSourceSharesAreZero_bkava() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + earnKeeper := newFakeEarnKeeper() // no vault, so no source shares + liquidKeeper := newFakeLiquidKeeper() + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + previousIndexes := types.MultiRewardIndexes{ + { + CollateralType: vaultDenom1, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + { + CollateralType: vaultDenom2, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.02"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + } + suite.storeGlobalEarnIndexes(previousIndexes) + previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime) + suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime) + + firstAccrualTime := previousAccrualTime.Add(7 * time.Second) + suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime) + + period := types.NewMultiRewardPeriod( + true, + "bkava", + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(c("earn", 2000), c("ukava", 1000)), // same denoms as in global indexes + ) + + // TODO: There are no bkava vaults to iterate over, so the accrual times are + // not updated + suite.keeper.AccumulateEarnRewards(suite.ctx, period) + + // check time and factors + + suite.storedTimeEquals(vaultDenom1, firstAccrualTime) + suite.storedTimeEquals(vaultDenom2, firstAccrualTime) + + expected, f := previousIndexes.Get(vaultDenom1) + suite.True(f) + suite.storedIndexesEqual(vaultDenom1, expected) + + expected, f = previousIndexes.Get(vaultDenom2) + suite.True(f) + suite.storedIndexesEqual(vaultDenom2, expected) +} + func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist() { vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper().addVault(vaultDenom, earntypes.NewVaultShare(vaultDenom, d("1000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) period := types.NewMultiRewardPeriod( true, @@ -220,11 +581,70 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist() { }) } +func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bkava() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + earnKeeper := newFakeEarnKeeper(). + addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))). + addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000"))) + + liquidKeeper := newFakeLiquidKeeper(). + addDerivative(vaultDenom1, i(1000000)). + addDerivative(vaultDenom2, i(1000000)) + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + period := types.NewMultiRewardPeriod( + true, + "bkava", + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(c("earn", 2000), c("ukava", 1000)), + ) + + firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime) + + suite.keeper.AccumulateEarnRewards(suite.ctx, period) + + // After the first accumulation only the current block time should be stored. + // The indexes will be empty as no time has passed since the previous block because it didn't exist. + suite.storedTimeEquals(vaultDenom1, firstAccrualTime) + suite.storedTimeEquals(vaultDenom2, firstAccrualTime) + + suite.storedIndexesEqual(vaultDenom1, nil) + suite.storedIndexesEqual(vaultDenom2, nil) + + secondAccrualTime := firstAccrualTime.Add(10 * time.Second) + suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime) + + suite.keeper.AccumulateEarnRewards(suite.ctx, period) + + // After the second accumulation both current block time and indexes should be stored. + suite.storedTimeEquals(vaultDenom1, secondAccrualTime) + suite.storedTimeEquals(vaultDenom2, secondAccrualTime) + + expectedIndexes := types.RewardIndexes{ + { + CollateralType: "earn", + RewardFactor: d("0.01"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.005"), + }, + } + + suite.storedIndexesEqual(vaultDenom1, expectedIndexes) + suite.storedIndexesEqual(vaultDenom2, expectedIndexes) +} + func (suite *AccumulateEarnRewardsTests) TestNoPanicWhenStateDoesNotExist() { vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) period := types.NewMultiRewardPeriod( true, @@ -248,11 +668,47 @@ func (suite *AccumulateEarnRewardsTests) TestNoPanicWhenStateDoesNotExist() { suite.storedIndexesEqual(vaultDenom, nil) } +func (suite *AccumulateEarnRewardsTests) TestNoPanicWhenStateDoesNotExist_bkava() { + vaultDenom1 := "bkava-meow" + vaultDenom2 := "bkava-woof" + + earnKeeper := newFakeEarnKeeper() + liquidKeeper := newFakeLiquidKeeper() + + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper) + + period := types.NewMultiRewardPeriod( + true, + "bkava", + time.Unix(0, 0), // ensure the test is within start and end times + distantFuture, + cs(), + ) + + accrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + suite.ctx = suite.ctx.WithBlockTime(accrualTime) + + // Accumulate with no earn shares and no rewards per second will result in no increment to the indexes. + // No increment and no previous indexes stored, results in an updated of nil. Setting this in the state panics. + // Check there is no panic. + suite.NotPanics(func() { + // This does not update any state, as there are no bkava vaults + // to iterate over, denoms are unknown + suite.keeper.AccumulateEarnRewards(suite.ctx, period) + }) + + // Times are not stored for vaults with no state + suite.storedTimeEquals(vaultDenom1, time.Time{}) + suite.storedTimeEquals(vaultDenom2, time.Time{}) + suite.storedIndexesEqual(vaultDenom1, nil) + suite.storedIndexesEqual(vaultDenom2, nil) +} + func (suite *AccumulateEarnRewardsTests) TestNoAccumulationWhenBeforeStartTime() { vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper().addVault(vaultDenom, earntypes.NewVaultShare(vaultDenom, d("1000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) previousIndexes := types.MultiRewardIndexes{ { @@ -298,7 +754,7 @@ func (suite *AccumulateEarnRewardsTests) TestPanicWhenCurrentTimeLessThanPreviou vaultDenom := "usdx" earnKeeper := newFakeEarnKeeper().addVault(vaultDenom, earntypes.NewVaultShare(vaultDenom, d("1000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_earn_proportional_test.go b/x/incentive/keeper/rewards_earn_proportional_test.go new file mode 100644 index 00000000..bdae0469 --- /dev/null +++ b/x/incentive/keeper/rewards_earn_proportional_test.go @@ -0,0 +1,86 @@ +package keeper_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/require" +) + +func TestGetProportionalRewardPeriod(t *testing.T) { + tests := []struct { + name string + giveRewardPeriod types.MultiRewardPeriod + giveTotalBkavaSupply sdk.Int + giveSingleBkavaSupply sdk.Int + wantRewardsPerSecond sdk.DecCoins + }{ + { + "full amount", + types.NewMultiRewardPeriod( + true, + "", + time.Time{}, + time.Time{}, + cs(c("ukava", 100), c("hard", 200)), + ), + i(100), + i(100), + toDcs(c("ukava", 100), c("hard", 200)), + }, + { + "3/4 amount", + types.NewMultiRewardPeriod( + true, + "", + time.Time{}, + time.Time{}, + cs(c("ukava", 100), c("hard", 200)), + ), + i(10_000000), + i(7_500000), + toDcs(c("ukava", 75), c("hard", 150)), + }, + { + "half amount", + types.NewMultiRewardPeriod( + true, + "", + time.Time{}, + time.Time{}, + cs(c("ukava", 100), c("hard", 200)), + ), + i(100), + i(50), + toDcs(c("ukava", 50), c("hard", 100)), + }, + { + "under 1 unit", + types.NewMultiRewardPeriod( + true, + "", + time.Time{}, + time.Time{}, + cs(c("ukava", 100), c("hard", 200)), + ), + i(1000), // total bkava + i(1), // bkava supply of this specific vault + dcs(dc("ukava", "0.1"), dc("hard", "0.2")), // rewards per second rounded to 0 if under 1ukava/1hard + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rewardsPerSecond := keeper.GetProportionalRewardsPerSecond( + tt.giveRewardPeriod, + tt.giveTotalBkavaSupply, + tt.giveSingleBkavaSupply, + ) + + require.Equal(t, tt.wantRewardsPerSecond, rewardsPerSecond) + }) + } +} diff --git a/x/incentive/keeper/rewards_earn_sync_test.go b/x/incentive/keeper/rewards_earn_sync_test.go index 9269cf24..e557f4a8 100644 --- a/x/incentive/keeper/rewards_earn_sync_test.go +++ b/x/incentive/keeper/rewards_earn_sync_test.go @@ -324,7 +324,7 @@ func (suite *SynchronizeEarnRewardTests) TestGetSyncedClaim_ClaimUnchangedWhenNo earnKeeper := newFakeEarnKeeper(). addDeposit(owner, earntypes.NewVaultShare("usdx", d("1000000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) claim := types.EarnClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -357,7 +357,7 @@ func (suite *SynchronizeEarnRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss owner := arbitraryAddress() // owner has no shares in any vault - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, newFakeEarnKeeper()) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, newFakeEarnKeeper()) claim := types.EarnClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -419,7 +419,7 @@ func (suite *SynchronizeEarnRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss addVault(VaultDenom_2, earntypes.NewVaultShare(VaultDenom_2, d("1000000000"))). addDeposit(owner, earntypes.NewVaultShare(VaultDenom_1, d("1000000000"))). addDeposit(owner, earntypes.NewVaultShare(VaultDenom_2, d("1000000000"))) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, earnKeeper) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, earnKeeper) claim := types.EarnClaim{ BaseMultiClaim: types.BaseMultiClaim{ diff --git a/x/incentive/keeper/rewards_supply_accum_test.go b/x/incentive/keeper/rewards_supply_accum_test.go index 145cd68d..179a1b18 100644 --- a/x/incentive/keeper/rewards_supply_accum_test.go +++ b/x/incentive/keeper/rewards_supply_accum_test.go @@ -38,7 +38,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateUpdatedWhenBlockTimeHasIncre denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) suite.storeGlobalSupplyIndexes(types.MultiRewardIndexes{ { @@ -90,7 +90,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateUnchangedWhenBlockTimeHasNot denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -135,7 +135,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenSourceSharesAre denom := "bnb" hardKeeper := newFakeHardKeeper() // zero total supplys - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -181,7 +181,7 @@ func (suite *AccumulateSupplyRewardsTests) TestStateAddedWhenStateDoesNotExist() denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -224,7 +224,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoPanicWhenStateDoesNotExist() { denom := "bnb" hardKeeper := newFakeHardKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -252,7 +252,7 @@ func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenBeforeStartTime denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -298,7 +298,7 @@ func (suite *AccumulateSupplyRewardsTests) TestPanicWhenCurrentTimeLessThanPrevi denom := "bnb" hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_swap_accum_test.go b/x/incentive/keeper/rewards_swap_accum_test.go index c836b931..bb09e866 100644 --- a/x/incentive/keeper/rewards_swap_accum_test.go +++ b/x/incentive/keeper/rewards_swap_accum_test.go @@ -37,7 +37,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{ { @@ -89,7 +89,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -134,7 +134,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenSourceSharesAreZe pool := "btc:usdx" swapKeeper := newFakeSwapKeeper() // no pools, so no source shares - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -180,7 +180,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() { pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -223,7 +223,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() { pool := "btc:usdx" swapKeeper := newFakeSwapKeeper() - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) period := types.NewMultiRewardPeriod( true, @@ -251,7 +251,7 @@ func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenBeforeStartTime() pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) previousIndexes := types.MultiRewardIndexes{ { @@ -297,7 +297,7 @@ func (suite *AccumulateSwapRewardsTests) TestPanicWhenCurrentTimeLessThanPreviou pool := "btc:usdx" swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime) diff --git a/x/incentive/keeper/rewards_swap_sync_test.go b/x/incentive/keeper/rewards_swap_sync_test.go index 63570439..97fda931 100644 --- a/x/incentive/keeper/rewards_swap_sync_test.go +++ b/x/incentive/keeper/rewards_swap_sync_test.go @@ -323,7 +323,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUnchangedWhenNo swapKeeper := newFakeSwapKeeper(). addDeposit(poolID_1, owner, i(1e9)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -356,7 +356,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss owner := arbitraryAddress() // owner has no shares in any pool - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, newFakeSwapKeeper(), nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, newFakeSwapKeeper(), nil, nil, nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ @@ -416,7 +416,7 @@ func (suite *SynchronizeSwapRewardTests) TestGetSyncedClaim_ClaimUpdatedWhenMiss swapKeeper := newFakeSwapKeeper(). addDeposit(poolID_1, owner, i(1e9)). addDeposit(poolID_2, owner, i(1e9)) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper, nil, nil, nil) claim := types.SwapClaim{ BaseMultiClaim: types.BaseMultiClaim{ diff --git a/x/incentive/keeper/rewards_usdx_accum_test.go b/x/incentive/keeper/rewards_usdx_accum_test.go index 1fb67824..4b7283a6 100644 --- a/x/incentive/keeper/rewards_usdx_accum_test.go +++ b/x/incentive/keeper/rewards_usdx_accum_test.go @@ -34,7 +34,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) suite.storeGlobalUSDXIndexes(types.RewardIndexes{ { @@ -68,7 +68,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -104,7 +104,7 @@ func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenSourceSharesAreZe cType := "bnb-a" cdpKeeper := newFakeCDPKeeper() // zero total borrows - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -141,7 +141,7 @@ func (suite *AccumulateUSDXRewardsTests) TestStateAddedWhenStateDoesNotExist() { cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) period := types.NewRewardPeriod( true, @@ -174,7 +174,7 @@ func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenBeforeStartTime() cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) previousIndexes := types.RewardIndexes{ { @@ -211,7 +211,7 @@ func (suite *AccumulateUSDXRewardsTests) TestPanicWhenCurrentTimeLessThanPreviou cType := "bnb-a" cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1")) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil, nil, nil, nil) previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime) diff --git a/x/incentive/keeper/unit_test.go b/x/incentive/keeper/unit_test.go index 14f527cc..48ed96eb 100644 --- a/x/incentive/keeper/unit_test.go +++ b/x/incentive/keeper/unit_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "fmt" + "strings" "time" "github.com/cosmos/cosmos-sdk/codec" @@ -59,7 +60,7 @@ func (suite *unitTester) SetupSuite() { func (suite *unitTester) SetupTest() { suite.ctx = NewTestContext(suite.incentiveStoreKey) - suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil) + suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil) } func (suite *unitTester) TearDownTest() { @@ -67,8 +68,13 @@ func (suite *unitTester) TearDownTest() { suite.ctx = sdk.Context{} } -func (suite *unitTester) NewKeeper(paramSubspace types.ParamSubspace, bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper, svk types.SavingsKeeper, ek types.EarnKeeper) keeper.Keeper { - return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, bk, cdpk, hk, ak, stk, swk, svk, ek) +func (suite *unitTester) NewKeeper( + paramSubspace types.ParamSubspace, + bk types.BankKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, + ak types.AccountKeeper, stk types.StakingKeeper, swk types.SwapKeeper, + svk types.SavingsKeeper, lqk types.LiquidKeeper, ek types.EarnKeeper, +) keeper.Keeper { + return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, bk, cdpk, hk, ak, stk, swk, svk, lqk, ek) } func (suite *unitTester) storeGlobalBorrowIndexes(indexes types.MultiRewardIndexes) { @@ -412,6 +418,66 @@ func (k *fakeEarnKeeper) GetVaultAccountShares( return accShares, found } +func (k *fakeEarnKeeper) IterateVaultRecords( + ctx sdk.Context, + cb func(record earntypes.VaultRecord) (stop bool), +) { + for _, vaultShares := range k.vaultShares { + cb(earntypes.VaultRecord{ + TotalShares: vaultShares, + }) + } +} + +// fakeLiquidKeeper is a stub liquid keeper. +// It can be used to return values to the incentive keeper without having to initialize a full liquid keeper. +type fakeLiquidKeeper struct { + derivatives map[string]sdk.Int +} + +var _ types.LiquidKeeper = newFakeLiquidKeeper() + +func newFakeLiquidKeeper() *fakeLiquidKeeper { + return &fakeLiquidKeeper{ + derivatives: map[string]sdk.Int{}, + } +} + +func (k *fakeLiquidKeeper) addDerivative(denom string, supply sdk.Int) *fakeLiquidKeeper { + k.derivatives[denom] = supply + return k +} + +func (k *fakeLiquidKeeper) IsDerivativeDenom(ctx sdk.Context, denom string) bool { + return strings.HasPrefix(denom, "bkava-") +} + +func (k *fakeLiquidKeeper) GetAllDerivativeDenoms(ctx sdk.Context) (denoms []string) { + for denom := range k.derivatives { + denoms = append(denoms, denom) + } + + return denoms +} + +func (k *fakeLiquidKeeper) GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int { + totalSupply := sdk.ZeroInt() + for _, supply := range k.derivatives { + totalSupply = totalSupply.Add(supply) + } + + return totalSupply +} + +func (k *fakeLiquidKeeper) GetDerivativeSupply(ctx sdk.Context, denom string) sdk.Int { + supply, found := k.derivatives[denom] + if !found { + return sdk.ZeroInt() + } + + return supply +} + // Assorted Testing Data // note: amino panics when encoding times ≥ the start of year 10000. diff --git a/x/incentive/types/accumulator.go b/x/incentive/types/accumulator.go index 69c774eb..fabf2329 100644 --- a/x/incentive/types/accumulator.go +++ b/x/incentive/types/accumulator.go @@ -31,12 +31,29 @@ func NewAccumulator(previousAccrual time.Time, indexes RewardIndexes) *Accumulat // // totalSourceShares is the sum of all users' source shares. For example:total btcb supplied to hard, total usdx borrowed from all bnb CDPs, or total shares in a swap pool. func (acc *Accumulator) Accumulate(period MultiRewardPeriod, totalSourceShares sdk.Dec, currentTime time.Time) { - accumulationDuration := acc.getTimeElapsedWithinLimits(acc.PreviousAccumulationTime, currentTime, period.Start, period.End) + acc.AccumulateDecCoins( + period.Start, + period.End, + sdk.NewDecCoinsFromCoins(period.RewardsPerSecond...), + totalSourceShares, + currentTime, + ) +} - indexesIncrement := acc.calculateNewRewards(period.RewardsPerSecond, totalSourceShares, accumulationDuration) +// AccumulateDecCoins +func (acc *Accumulator) AccumulateDecCoins( + periodStart time.Time, + periodEnd time.Time, + periodRewardsPerSecond sdk.DecCoins, + totalSourceShares sdk.Dec, + currentTime time.Time, +) { + accumulationDuration := acc.getTimeElapsedWithinLimits(acc.PreviousAccumulationTime, currentTime, periodStart, periodEnd) + + indexesIncrement := acc.calculateNewRewards(periodRewardsPerSecond, totalSourceShares, accumulationDuration) acc.Indexes = acc.Indexes.Add(indexesIncrement) - acc.PreviousAccumulationTime = minTime(period.End, currentTime) + acc.PreviousAccumulationTime = minTime(periodEnd, currentTime) } // getTimeElapsedWithinLimits returns the duration between start and end times, capped by min and max times. @@ -59,7 +76,7 @@ func (*Accumulator) getTimeElapsedWithinLimits(start, end, limitMin, limitMax ti // The total rewards to distribute in this block are given by reward rate * duration. This value divided by the sum of all source shares to give // total rewards per source share, which is what the indexes store. // Note, duration is rounded to the nearest second to keep rewards calculation consistent with kava-7. -func (*Accumulator) calculateNewRewards(rewardsPerSecond sdk.Coins, totalSourceShares sdk.Dec, duration time.Duration) RewardIndexes { +func (*Accumulator) calculateNewRewards(rewardsPerSecond sdk.DecCoins, totalSourceShares sdk.Dec, duration time.Duration) RewardIndexes { if totalSourceShares.LTE(sdk.ZeroDec()) { // When there is zero source shares, there is no users with deposits/borrows/delegations to pay out the current block's rewards to. // So drop the rewards and pay out nothing. @@ -93,10 +110,10 @@ func maxTime(t1, t2 time.Time) time.Time { } // newRewardIndexesFromCoins is a helper function to initialize a RewardIndexes slice with the values from a Coins slice. -func newRewardIndexesFromCoins(coins sdk.Coins) RewardIndexes { +func newRewardIndexesFromCoins(coins sdk.DecCoins) RewardIndexes { var indexes RewardIndexes for _, coin := range coins { - indexes = append(indexes, NewRewardIndex(coin.Denom, coin.Amount.ToDec())) + indexes = append(indexes, NewRewardIndex(coin.Denom, coin.Amount)) } return indexes } diff --git a/x/incentive/types/accumulator_test.go b/x/incentive/types/accumulator_test.go index 17cecca1..f1792847 100644 --- a/x/incentive/types/accumulator_test.go +++ b/x/incentive/types/accumulator_test.go @@ -187,7 +187,11 @@ func TestAccumulator(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { acc := &Accumulator{} - indexes := acc.calculateNewRewards(tc.args.rewardsPerSecond, tc.args.totalSourceShares, tc.args.duration) + indexes := acc.calculateNewRewards( + sdk.NewDecCoinsFromCoins(tc.args.rewardsPerSecond...), + tc.args.totalSourceShares, + tc.args.duration, + ) require.Equal(t, tc.expected, indexes) }) diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index 4a38486f..6cfb8d9c 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -68,6 +68,14 @@ type SavingsKeeper interface { type EarnKeeper interface { GetVaultTotalShares(ctx sdk.Context, denom string) (shares earntypes.VaultShare, found bool) GetVaultAccountShares(ctx sdk.Context, acc sdk.AccAddress) (shares earntypes.VaultShares, found bool) + IterateVaultRecords(ctx sdk.Context, cb func(record earntypes.VaultRecord) (stop bool)) +} + +// LiquidKeeper defines the required methods needed by this modules keeper +type LiquidKeeper interface { + IsDerivativeDenom(ctx sdk.Context, denom string) bool + GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int + GetDerivativeSupply(ctx sdk.Context, denom string) sdk.Int } // AccountKeeper expected interface for the account keeper (noalias)