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
This commit is contained in:
Derrick Lee 2022-09-23 09:38:22 -07:00 committed by GitHub
parent 26a4b93588
commit 651de460ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 837 additions and 77 deletions

View File

@ -619,6 +619,8 @@ func NewApp(
app.stakingKeeper,
&swapKeeper,
&savingsKeeper,
// TODO: Liquid keeper
nil,
&earnKeeper,
)

View File

@ -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))

View File

@ -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{

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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{

View File

@ -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"),

View File

@ -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)
}
}

View File

@ -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(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)

View File

@ -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)
})
}
}

View File

@ -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{

View File

@ -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)

View File

@ -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)

View File

@ -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{

View File

@ -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)

View File

@ -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.

View File

@ -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
}

View File

@ -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)
})

View File

@ -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)