Use different accumulator for earn (#1395)

* Add accumulators

* Move accumulator back to keeper package

* Add earn specific accumulators

* Move store methods to sub-package

* Move earn accumulator

* Rename accumulator files

* Add store doc comment

* Add earn accumulator tests, panic if accumulator not used with earn claim type

* Update earn accumulator tests to use new methods

* Add staking test for earn accumulator

* Add test for accumulator proportional rewards

* Remove old copy of GetProportionalRewardsPerSecond

* Add test for basic accumulator

* Fix AddIncentiveMultiRewardPeriod replacement

* Deduplicate base earn reward accumulator

* Check errors in tests

* Validate RewardPeriods in Params.Validate()

* Use adapter to fetch earn total shares
This commit is contained in:
Derrick Lee 2022-11-29 14:23:33 -08:00 committed by GitHub
parent 44a90a8ef9
commit cf009647e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2253 additions and 417 deletions

View File

@ -39,7 +39,9 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
// New generic RewardPeriods
for _, mrp := range params.RewardPeriods {
for _, rp := range mrp.RewardPeriods {
k.AccumulateRewards(ctx, mrp.ClaimType, rp)
if err := k.AccumulateRewards(ctx, mrp.ClaimType, rp); err != nil {
panic(fmt.Errorf("failed to accumulate rewards for claim type %s: %w", mrp.ClaimType, err))
}
}
}
}

View File

@ -46,12 +46,12 @@ func InitGenesis(
// Set Claims of all types
for _, claim := range gs.Claims {
k.SetClaim(ctx, claim)
k.Store.SetClaim(ctx, claim)
}
// Set AccrualTimes of all types
for _, accrualTime := range gs.AccrualTimes {
k.SetRewardAccrualTime(
k.Store.SetRewardAccrualTime(
ctx,
accrualTime.ClaimType,
accrualTime.CollateralType,
@ -61,7 +61,7 @@ func InitGenesis(
// Set RewardIndexes of all types
for _, rewardIndex := range gs.RewardIndexes {
k.SetRewardIndexes(ctx, rewardIndex.ClaimType, rewardIndex.CollateralType, rewardIndex.RewardIndexes)
k.Store.SetRewardIndexes(ctx, rewardIndex.ClaimType, rewardIndex.CollateralType, rewardIndex.RewardIndexes)
}
// Legacy claims and indexes below
@ -168,9 +168,9 @@ func InitGenesis(
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
params := k.GetParams(ctx)
claims := k.GetAllClaims(ctx)
accrualTimes := k.GetAllRewardAccrualTimes(ctx)
rewardIndexes := k.GetRewardIndexes(ctx)
claims := k.Store.GetAllClaims(ctx)
accrualTimes := k.Store.GetAllRewardAccrualTimes(ctx)
rewardIndexes := k.Store.GetRewardIndexes(ctx)
usdxClaims := k.GetAllUSDXMintingClaims(ctx)
usdxRewardState := getUSDXMintingGenesisRewardState(ctx, k)

View File

@ -0,0 +1,19 @@
package accumulators_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var distantFuture = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC)
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...) }

View File

@ -0,0 +1,63 @@
package accumulators
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/keeper/adapters"
"github.com/kava-labs/kava/x/incentive/keeper/store"
"github.com/kava-labs/kava/x/incentive/types"
)
// BasicAccumulator is a default implementation of the RewardAccumulator
// interface. This applies to all claim types except for those with custom
// accumulator logic e.g. Earn.
type BasicAccumulator struct {
store store.IncentiveStore
adapters adapters.SourceAdapters
}
var _ types.RewardAccumulator = BasicAccumulator{}
// NewBasicAccumulator returns a new BasicAccumulator.
func NewBasicAccumulator(
store store.IncentiveStore,
adapters adapters.SourceAdapters,
) BasicAccumulator {
return BasicAccumulator{
store: store,
adapters: adapters,
}
}
// AccumulateRewards 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 BasicAccumulator) AccumulateRewards(
ctx sdk.Context,
claimType types.ClaimType,
rewardPeriod types.MultiRewardPeriod,
) error {
previousAccrualTime, found := k.store.GetRewardAccrualTime(ctx, claimType, rewardPeriod.CollateralType)
if !found {
previousAccrualTime = ctx.BlockTime()
}
indexes, found := k.store.GetRewardIndexesOfClaimType(ctx, claimType, rewardPeriod.CollateralType)
if !found {
indexes = types.RewardIndexes{}
}
acc := types.NewAccumulator(previousAccrualTime, indexes)
totalSource := k.adapters.TotalSharesBySource(ctx, claimType, rewardPeriod.CollateralType)
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
k.store.SetRewardAccrualTime(ctx, claimType, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
if len(acc.Indexes) > 0 {
// the store panics when setting empty or nil indexes
k.store.SetRewardIndexes(ctx, claimType, rewardPeriod.CollateralType, acc.Indexes)
}
return nil
}

View File

@ -0,0 +1,404 @@
package accumulators_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
"github.com/kava-labs/kava/app"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types"
swaptypes "github.com/kava-labs/kava/x/swap/types"
)
type BasicAccumulatorTestSuite struct {
testutil.IntegrationTester
keeper testutil.TestKeeper
userAddrs []sdk.AccAddress
valAddrs []sdk.ValAddress
pool string
}
func TestBasicAccumulatorTestSuite(t *testing.T) {
suite.Run(t, new(BasicAccumulatorTestSuite))
}
func (suite *BasicAccumulatorTestSuite) SetupTest() {
suite.IntegrationTester.SetupTest()
suite.keeper = testutil.TestKeeper{
Keeper: suite.App.GetIncentiveKeeper(),
}
_, addrs := app.GeneratePrivKeyAddressPairs(5)
suite.userAddrs = addrs[0:2]
suite.valAddrs = []sdk.ValAddress{
sdk.ValAddress(addrs[2]),
sdk.ValAddress(addrs[3]),
}
poolDenomA := "btc"
poolDenomB := "usdx"
// Setup app with test state
authBuilder := app.NewAuthBankGenesisBuilder().
WithSimpleAccount(addrs[0], cs(
c("ukava", 1e12),
c(poolDenomA, 1e12),
c(poolDenomB, 1e12),
)).
WithSimpleAccount(addrs[1], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[2], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[3], cs(c("ukava", 1e12)))
incentiveBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.GenesisTime).
WithSimpleRewardPeriod(types.CLAIM_TYPE_EARN, "bkava", cs())
savingsBuilder := testutil.NewSavingsGenesisBuilder().
WithSupportedDenoms("bkava")
earnBuilder := testutil.NewEarnGenesisBuilder().
WithAllowedVaults(earntypes.AllowedVault{
Denom: "bkava",
Strategies: earntypes.StrategyTypes{earntypes.STRATEGY_TYPE_SAVINGS},
IsPrivateVault: false,
AllowedDepositors: nil,
})
stakingBuilder := testutil.NewStakingGenesisBuilder()
mintBuilder := testutil.NewMintGenesisBuilder().
WithInflationMax(sdk.OneDec()).
WithInflationMin(sdk.OneDec()).
WithMinter(sdk.OneDec(), sdk.ZeroDec()).
WithMintDenom("ukava")
suite.StartChainWithBuilders(
authBuilder,
incentiveBuilder,
savingsBuilder,
earnBuilder,
stakingBuilder,
mintBuilder,
)
suite.pool = swaptypes.PoolID(poolDenomA, poolDenomB)
swapKeeper := suite.App.GetSwapKeeper()
swapKeeper.SetParams(suite.Ctx, swaptypes.NewParams(
swaptypes.NewAllowedPools(
swaptypes.NewAllowedPool(poolDenomA, poolDenomB),
),
sdk.ZeroDec(),
))
}
func TestAccumulateSwapRewards(t *testing.T) {
suite.Run(t, new(BasicAccumulatorTestSuite))
}
func (suite *BasicAccumulatorTestSuite) TestStateUpdatedWhenBlockTimeHasIncreased() {
pool := "btc:usdx"
err := suite.DeliverSwapMsgDeposit(suite.userAddrs[0], c("btc", 1e6), c("usdx", 1e6), d("1.0"))
suite.Require().NoError(err)
suite.keeper.StoreGlobalIndexes(
suite.Ctx,
types.CLAIM_TYPE_SWAP,
types.MultiRewardIndexes{
{
CollateralType: pool,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
},
)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
suite.Ctx = suite.Ctx.WithBlockTime(newAccrualTime)
period := types.NewMultiRewardPeriod(
true,
pool,
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)), // same denoms as in global indexes
)
err = suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, newAccrualTime)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("7.22"),
},
{
CollateralType: "ukava",
RewardFactor: d("3.64"),
},
})
}
func (suite *BasicAccumulatorTestSuite) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
pool := "btc:usdx"
err := suite.DeliverSwapMsgDeposit(suite.userAddrs[0], c("btc", 1e6), c("usdx", 1e6), d("1.0"))
suite.Require().NoError(err)
previousIndexes := types.MultiRewardIndexes{
{
CollateralType: pool,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(
suite.Ctx,
types.CLAIM_TYPE_SWAP,
previousIndexes,
)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
suite.Ctx = suite.Ctx.WithBlockTime(previousAccrualTime)
period := types.NewMultiRewardPeriod(
true,
pool,
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)), // same denoms as in global indexes
)
err = suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
expected, f := previousIndexes.Get(pool)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, expected)
}
func (suite *BasicAccumulatorTestSuite) TestNoAccumulationWhenSourceSharesAreZero() {
pool := "btc:usdx"
previousIndexes := types.MultiRewardIndexes{
{
CollateralType: pool,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(
suite.Ctx,
types.CLAIM_TYPE_SWAP, previousIndexes)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
suite.Ctx = suite.Ctx.WithBlockTime(firstAccrualTime)
period := types.NewMultiRewardPeriod(
true,
pool,
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)), // same denoms as in global indexes
)
err := suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, firstAccrualTime)
expected, f := previousIndexes.Get(pool)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, expected)
}
func (suite *BasicAccumulatorTestSuite) TestStateAddedWhenStateDoesNotExist() {
pool := "btc:usdx"
err := suite.DeliverSwapMsgDeposit(suite.userAddrs[0], c("btc", 1e6), c("usdx", 1e6), d("1.0"))
suite.Require().NoError(err)
period := types.NewMultiRewardPeriod(
true,
pool,
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)),
)
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.Ctx = suite.Ctx.WithBlockTime(firstAccrualTime)
err = suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// 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(types.CLAIM_TYPE_SWAP, pool, firstAccrualTime)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, nil)
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
suite.Ctx = suite.Ctx.WithBlockTime(secondAccrualTime)
err = suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// After the second accumulation both current block time and indexes should be stored.
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, secondAccrualTime)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.01"),
},
})
}
func (suite *BasicAccumulatorTestSuite) TestNoPanicWhenStateDoesNotExist() {
pool := "btc:usdx"
period := types.NewMultiRewardPeriod(
true,
pool,
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 swap 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() {
err := suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
})
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, accrualTime)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, nil)
}
func (suite *BasicAccumulatorTestSuite) TestNoAccumulationWhenBeforeStartTime() {
pool := "btc:usdx"
err := suite.DeliverSwapMsgDeposit(suite.userAddrs[0], c("btc", 1e6), c("usdx", 1e6), d("1.0"))
suite.Require().NoError(err)
previousIndexes := types.MultiRewardIndexes{
{
CollateralType: pool,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "swap",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(
suite.Ctx,
types.CLAIM_TYPE_SWAP, previousIndexes)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
period := types.NewMultiRewardPeriod(
true,
pool,
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)),
)
suite.Ctx = suite.Ctx.WithBlockTime(firstAccrualTime)
err = suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
suite.Require().NoError(err)
// The accrual time should be updated, but the indexes unchanged
suite.StoredTimeEquals(types.CLAIM_TYPE_SWAP, pool, firstAccrualTime)
expectedIndexes, f := previousIndexes.Get(pool)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_SWAP, pool, expectedIndexes)
}
func (suite *BasicAccumulatorTestSuite) TestPanicWhenCurrentTimeLessThanPrevious() {
pool := "btc:usdx"
err := suite.DeliverSwapMsgDeposit(suite.userAddrs[0], c("btc", 1e6), c("usdx", 1e6), d("1.0"))
suite.Require().NoError(err)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_SWAP, pool, previousAccrualTime)
firstAccrualTime := time.Time{}
period := types.NewMultiRewardPeriod(
true,
pool,
time.Time{}, // start time after accrual time
distantFuture,
cs(c("swap", 2000), c("ukava", 1000)),
)
suite.Ctx = suite.Ctx.WithBlockTime(firstAccrualTime)
suite.Panics(func() {
suite.keeper.AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_SWAP, period)
})
}

View File

@ -0,0 +1,245 @@
package accumulators
import (
"errors"
"fmt"
"sort"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/keeper/adapters"
"github.com/kava-labs/kava/x/incentive/keeper/store"
"github.com/kava-labs/kava/x/incentive/types"
)
// EarnAccumulator is an accumulator for Earn claim types. This includes
// claiming staking rewards and reward distribution for liquid kava.
type EarnAccumulator struct {
store store.IncentiveStore
liquidKeeper types.LiquidKeeper
earnKeeper types.EarnKeeper
adapters adapters.SourceAdapters
}
var _ types.RewardAccumulator = EarnAccumulator{}
// NewEarnAccumulator returns a new EarnAccumulator.
func NewEarnAccumulator(
store store.IncentiveStore,
liquidKeeper types.LiquidKeeper,
earnKeeper types.EarnKeeper,
adapters adapters.SourceAdapters,
) EarnAccumulator {
return EarnAccumulator{
store: store,
liquidKeeper: liquidKeeper,
earnKeeper: earnKeeper,
adapters: adapters,
}
}
// AccumulateRewards 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 (a EarnAccumulator) AccumulateRewards(
ctx sdk.Context,
claimType types.ClaimType,
rewardPeriod types.MultiRewardPeriod,
) error {
if claimType != types.CLAIM_TYPE_EARN {
panic(fmt.Sprintf(
"invalid claim type for earn accumulator, expected %s but got %s",
types.CLAIM_TYPE_EARN,
claimType,
))
}
if rewardPeriod.CollateralType == "bkava" {
return a.accumulateEarnBkavaRewards(ctx, rewardPeriod)
}
// Non bkava vaults use the basic accumulator.
return NewBasicAccumulator(a.store, a.adapters).AccumulateRewards(ctx, claimType, rewardPeriod)
}
// accumulateEarnBkavaRewards does the same as AccumulateEarnRewards but for
// *all* bkava vaults.
func (k EarnAccumulator) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
// 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) {
if k.liquidKeeper.IsDerivativeDenom(ctx, record.TotalShares.Denom) {
bkavaVaultsDenoms[record.TotalShares.Denom] = true
}
return false
})
// bkava vault denoms from past incentive indexes, may include vaults
// that were fully withdrawn.
k.store.IterateRewardIndexesByClaimType(
ctx,
types.CLAIM_TYPE_EARN,
func(reward types.TypedRewardIndexes) (stop bool) {
if k.liquidKeeper.IsDerivativeDenom(ctx, reward.CollateralType) {
bkavaVaultsDenoms[reward.CollateralType] = true
}
return false
})
totalBkavaValue, err := k.liquidKeeper.GetTotalDerivativeValue(ctx)
if err != nil {
return err
}
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 {
derivativeValue, err := k.liquidKeeper.GetDerivativeValue(ctx, bkavaDenom)
if err != nil {
return err
}
k.accumulateBkavaEarnRewards(
ctx,
bkavaDenom,
rewardPeriod.Start,
rewardPeriod.End,
GetProportionalRewardsPerSecond(
rewardPeriod,
totalBkavaValue.Amount,
derivativeValue.Amount,
),
)
}
return nil
}
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
}
func (k EarnAccumulator) accumulateBkavaEarnRewards(
ctx sdk.Context,
collateralType string,
periodStart time.Time,
periodEnd time.Time,
periodRewardsPerSecond sdk.DecCoins,
) {
// Collect staking rewards for this validator, does not have any start/end
// period time restrictions.
stakingRewards := k.collectDerivativeStakingRewards(ctx, collateralType)
// Collect incentive rewards
// **Total rewards** for vault per second, NOT per share
perSecondRewards := k.collectPerSecondRewards(
ctx,
collateralType,
periodStart,
periodEnd,
periodRewardsPerSecond,
)
// **Total rewards** for vault per second, NOT per share
rewards := stakingRewards.Add(perSecondRewards...)
// Distribute rewards by incrementing indexes
indexes, found := k.store.GetRewardIndexesOfClaimType(ctx, types.CLAIM_TYPE_EARN, collateralType)
if !found {
indexes = types.RewardIndexes{}
}
totalSourceShares := k.adapters.TotalSharesBySource(ctx, types.CLAIM_TYPE_EARN, collateralType)
var increment types.RewardIndexes
if totalSourceShares.GT(sdk.ZeroDec()) {
// Divide total rewards by total shares to get the reward **per share**
// Leave as nil if no source shares
increment = types.NewRewardIndexesFromCoins(rewards).Quo(totalSourceShares)
}
updatedIndexes := indexes.Add(increment)
if len(updatedIndexes) > 0 {
// the store panics when setting empty or nil indexes
k.store.SetRewardIndexes(ctx, types.CLAIM_TYPE_EARN, collateralType, updatedIndexes)
}
}
func (k EarnAccumulator) collectDerivativeStakingRewards(ctx sdk.Context, collateralType string) sdk.DecCoins {
rewards, err := k.liquidKeeper.CollectStakingRewardsByDenom(ctx, collateralType, types.IncentiveMacc)
if err != nil {
if !errors.Is(err, distrtypes.ErrNoValidatorDistInfo) &&
!errors.Is(err, distrtypes.ErrEmptyDelegationDistInfo) {
panic(fmt.Sprintf("failed to collect staking rewards for %s: %s", collateralType, err))
}
// otherwise there's no validator or delegation yet
rewards = nil
}
return sdk.NewDecCoinsFromCoins(rewards...)
}
func (k EarnAccumulator) collectPerSecondRewards(
ctx sdk.Context,
collateralType string,
periodStart time.Time,
periodEnd time.Time,
periodRewardsPerSecond sdk.DecCoins,
) sdk.DecCoins {
previousAccrualTime, found := k.store.GetRewardAccrualTime(ctx, types.CLAIM_TYPE_EARN, collateralType)
if !found {
previousAccrualTime = ctx.BlockTime()
}
rewards, accumulatedTo := types.CalculatePerSecondRewards(
periodStart,
periodEnd,
periodRewardsPerSecond,
previousAccrualTime,
ctx.BlockTime(),
)
k.store.SetRewardAccrualTime(ctx, types.CLAIM_TYPE_EARN, collateralType, accumulatedTo)
// Don't need to move funds as they're assumed to be in the IncentiveMacc module account already.
return rewards
}

View File

@ -0,0 +1,667 @@
package accumulators_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/kava-labs/kava/app"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/keeper/accumulators"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types"
)
type AccumulateEarnRewardsIntegrationTests struct {
testutil.IntegrationTester
keeper testutil.TestKeeper
userAddrs []sdk.AccAddress
valAddrs []sdk.ValAddress
}
func TestAccumulateEarnRewardsIntegrationTests(t *testing.T) {
suite.Run(t, new(AccumulateEarnRewardsIntegrationTests))
}
func (suite *AccumulateEarnRewardsIntegrationTests) SetupTest() {
suite.IntegrationTester.SetupTest()
suite.keeper = testutil.TestKeeper{
Keeper: suite.App.GetIncentiveKeeper(),
}
_, addrs := app.GeneratePrivKeyAddressPairs(5)
suite.userAddrs = addrs[0:2]
suite.valAddrs = []sdk.ValAddress{
sdk.ValAddress(addrs[2]),
sdk.ValAddress(addrs[3]),
}
// Setup app with test state
authBuilder := app.NewAuthBankGenesisBuilder().
WithSimpleAccount(addrs[0], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[1], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[2], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[3], cs(c("ukava", 1e12)))
incentiveBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.GenesisTime).
WithSimpleRewardPeriod(types.CLAIM_TYPE_EARN, "bkava", cs())
savingsBuilder := testutil.NewSavingsGenesisBuilder().
WithSupportedDenoms("bkava")
earnBuilder := testutil.NewEarnGenesisBuilder().
WithAllowedVaults(earntypes.AllowedVault{
Denom: "bkava",
Strategies: earntypes.StrategyTypes{earntypes.STRATEGY_TYPE_SAVINGS},
IsPrivateVault: false,
AllowedDepositors: nil,
})
stakingBuilder := testutil.NewStakingGenesisBuilder()
mintBuilder := testutil.NewMintGenesisBuilder().
WithInflationMax(sdk.OneDec()).
WithInflationMin(sdk.OneDec()).
WithMinter(sdk.OneDec(), sdk.ZeroDec()).
WithMintDenom("ukava")
suite.StartChainWithBuilders(
authBuilder,
incentiveBuilder,
savingsBuilder,
earnBuilder,
stakingBuilder,
mintBuilder,
)
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
suite.AddIncentiveMultiRewardPeriod(
types.CLAIM_TYPE_EARN,
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
),
)
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 800000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 200000))
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], derivative0, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[1], derivative1, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
globalIndexes := types.MultiRewardIndexes{
{
CollateralType: derivative0.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
{
CollateralType: derivative1.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(suite.Ctx, types.CLAIM_TYPE_EARN, globalIndexes)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
val0 := suite.GetAbciValidator(suite.valAddrs[0])
val1 := suite.GetAbciValidator(suite.valAddrs[1])
// Mint tokens, distribute to validators, claim staking rewards
// 1 hour later
_, resBeginBlock := suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{
{
Validator: val0,
SignedLastBlock: true,
},
{
Validator: val1,
SignedLastBlock: true,
},
},
},
},
)
validatorRewards, _ := suite.GetBeginBlockClaimedStakingRewards(resBeginBlock)
suite.Require().Contains(validatorRewards, suite.valAddrs[1].String(), "there should be claim events for validator 0")
suite.Require().Contains(validatorRewards, suite.valAddrs[0].String(), "there should be claim events for validator 1")
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
stakingRewardIndexes0 := validatorRewards[suite.valAddrs[0].String()].
AmountOf("ukava").
ToDec().
Quo(derivative0.Amount.ToDec())
stakingRewardIndexes1 := validatorRewards[suite.valAddrs[1].String()].
AmountOf("ukava").
ToDec().
Quo(derivative1.Amount.ToDec())
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("7.22"),
},
{
CollateralType: "ukava",
RewardFactor: d("3.64").Add(stakingRewardIndexes0),
},
})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("7.22"),
},
{
CollateralType: "ukava",
RewardFactor: d("3.64").Add(stakingRewardIndexes1),
},
})
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestStateUpdatedWhenBlockTimeHasIncreased_partialDeposit() {
suite.AddIncentiveMultiRewardPeriod(
types.CLAIM_TYPE_EARN,
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
),
)
// 800000bkava0 minted, 700000 deposited
// 200000bkava1 minted, 100000 deposited
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 800000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 200000))
suite.NoError(err)
depositAmount0 := c(derivative0.Denom, 700000)
depositAmount1 := c(derivative1.Denom, 100000)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], depositAmount0, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[1], depositAmount1, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
globalIndexes := types.MultiRewardIndexes{
{
CollateralType: derivative0.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
{
CollateralType: derivative1.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(suite.Ctx, types.CLAIM_TYPE_EARN, globalIndexes)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
val0 := suite.GetAbciValidator(suite.valAddrs[0])
val1 := suite.GetAbciValidator(suite.valAddrs[1])
// Mint tokens, distribute to validators, claim staking rewards
// 1 hour later
_, resBeginBlock := suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{
{
Validator: val0,
SignedLastBlock: true,
},
{
Validator: val1,
SignedLastBlock: true,
},
},
},
},
)
validatorRewards, _ := suite.GetBeginBlockClaimedStakingRewards(resBeginBlock)
suite.Require().Contains(validatorRewards, suite.valAddrs[1].String(), "there should be claim events for validator 0")
suite.Require().Contains(validatorRewards, suite.valAddrs[0].String(), "there should be claim events for validator 1")
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
// Divided by deposit amounts, not bank supply amounts
stakingRewardIndexes0 := validatorRewards[suite.valAddrs[0].String()].
AmountOf("ukava").
ToDec().
Quo(depositAmount0.Amount.ToDec())
stakingRewardIndexes1 := validatorRewards[suite.valAddrs[1].String()].
AmountOf("ukava").
ToDec().
Quo(depositAmount1.Amount.ToDec())
// Slightly increased rewards due to less bkava deposited
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("8.248571428571428571"),
},
{
CollateralType: "ukava",
RewardFactor: d("4.154285714285714285").Add(stakingRewardIndexes0),
},
})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("14.42"),
},
{
CollateralType: "ukava",
RewardFactor: d("7.24").Add(stakingRewardIndexes1),
},
})
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 1000000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 1000000))
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], derivative0, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[1], derivative1, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
previousIndexes := types.MultiRewardIndexes{
{
CollateralType: derivative0.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
{
CollateralType: derivative1.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(suite.Ctx, types.CLAIM_TYPE_EARN, previousIndexes)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
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
)
// Must manually accumulate rewards as BeginBlockers only run when the block time increases
// This does not run any x/mint or x/distribution BeginBlockers
earnKeeper := suite.App.GetEarnKeeper()
err = accumulators.
NewEarnAccumulator(suite.keeper.Store, suite.App.GetLiquidKeeper(), &earnKeeper, suite.keeper.Adapters).
AccumulateRewards(suite.Ctx, types.CLAIM_TYPE_EARN, period)
suite.NoError(err)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
expected, f := previousIndexes.Get(derivative0.Denom)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, expected)
expected, f = previousIndexes.Get(derivative1.Denom)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, expected)
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestNoAccumulationWhenSourceSharesAreZero() {
suite.AddIncentiveMultiRewardPeriod(
types.CLAIM_TYPE_EARN,
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
),
)
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 1000000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 1000000))
suite.NoError(err)
// No earn deposits
previousIndexes := types.MultiRewardIndexes{
{
CollateralType: derivative0.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
{
CollateralType: derivative1.Denom,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "earn",
RewardFactor: d("0.02"),
},
{
CollateralType: "ukava",
RewardFactor: d("0.04"),
},
},
},
}
suite.keeper.StoreGlobalIndexes(suite.Ctx, types.CLAIM_TYPE_EARN, previousIndexes)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
val0 := suite.GetAbciValidator(suite.valAddrs[0])
val1 := suite.GetAbciValidator(suite.valAddrs[1])
// Mint tokens, distribute to validators, claim staking rewards
// 1 hour later
_, _ = suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{
{
Validator: val0,
SignedLastBlock: true,
},
{
Validator: val1,
SignedLastBlock: true,
},
},
},
},
)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
expected, f := previousIndexes.Get(derivative0.Denom)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, expected)
expected, f = previousIndexes.Get(derivative1.Denom)
suite.True(f)
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, expected)
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestStateAddedWhenStateDoesNotExist() {
suite.AddIncentiveMultiRewardPeriod(
types.CLAIM_TYPE_EARN,
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
),
)
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 1000000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 1000000))
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], derivative0, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[1], derivative1, earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
val0 := suite.GetAbciValidator(suite.valAddrs[0])
val1 := suite.GetAbciValidator(suite.valAddrs[1])
_, resBeginBlock := suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{
{
Validator: val0,
SignedLastBlock: true,
},
{
Validator: val1,
SignedLastBlock: true,
},
},
},
},
)
// After the second accumulation both current block time and indexes should be stored.
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
validatorRewards0, _ := suite.GetBeginBlockClaimedStakingRewards(resBeginBlock)
firstStakingRewardIndexes0 := validatorRewards0[suite.valAddrs[0].String()].
AmountOf("ukava").
ToDec().
Quo(derivative0.Amount.ToDec())
firstStakingRewardIndexes1 := validatorRewards0[suite.valAddrs[1].String()].
AmountOf("ukava").
ToDec().
Quo(derivative1.Amount.ToDec())
// 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(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
// First accumulation can have staking rewards, but no other rewards
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: firstStakingRewardIndexes0,
},
})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: firstStakingRewardIndexes1,
},
})
_, resBeginBlock = suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{
{
Validator: val0,
SignedLastBlock: true,
},
{
Validator: val1,
SignedLastBlock: true,
},
},
},
},
)
// After the second accumulation both current block time and indexes should be stored.
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, suite.Ctx.BlockTime())
validatorRewards1, _ := suite.GetBeginBlockClaimedStakingRewards(resBeginBlock)
secondStakingRewardIndexes0 := validatorRewards1[suite.valAddrs[0].String()].
AmountOf("ukava").
ToDec().
Quo(derivative0.Amount.ToDec())
secondStakingRewardIndexes1 := validatorRewards1[suite.valAddrs[1].String()].
AmountOf("ukava").
ToDec().
Quo(derivative1.Amount.ToDec())
// Second accumulation has both staking rewards and incentive rewards
// ukava incentive rewards: 3600 * 1000 / (2 * 1000000) == 1.8
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, types.RewardIndexes{
{
CollateralType: "ukava",
// Incentive rewards + both staking rewards
RewardFactor: d("1.8").Add(firstStakingRewardIndexes0).Add(secondStakingRewardIndexes0),
},
{
CollateralType: "earn",
RewardFactor: d("3.6"),
},
})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, types.RewardIndexes{
{
CollateralType: "ukava",
// Incentive rewards + both staking rewards
RewardFactor: d("1.8").Add(firstStakingRewardIndexes1).Add(secondStakingRewardIndexes1),
},
{
CollateralType: "earn",
RewardFactor: d("3.6"),
},
})
}
func (suite *AccumulateEarnRewardsIntegrationTests) TestNoPanicWhenStateDoesNotExist() {
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], c("ukava", 1000000))
suite.NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[1], suite.valAddrs[1], c("ukava", 1000000))
suite.NoError(err)
period := types.NewMultiRewardPeriod(
true,
"bkava",
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(),
)
// 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
err := suite.keeper.AccumulateEarnRewards(suite.Ctx, period)
suite.NoError(err)
})
// Times are not stored for vaults with no state
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative0.Denom, time.Time{})
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, derivative1.Denom, time.Time{})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative0.Denom, nil)
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, derivative1.Denom, nil)
}

View File

@ -1,11 +1,11 @@
package keeper_test
package accumulators_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/keeper/accumulators"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/stretchr/testify/require"
)
@ -74,7 +74,7 @@ func TestGetProportionalRewardPeriod(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rewardsPerSecond := keeper.GetProportionalRewardsPerSecond(
rewardsPerSecond := accumulators.GetProportionalRewardsPerSecond(
tt.giveRewardPeriod,
tt.giveTotalBkavaSupply,
tt.giveSingleBkavaSupply,

View File

@ -0,0 +1,193 @@
package accumulators_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
)
type EarnAccumulatorStakingRewardsTestSuite struct {
testutil.IntegrationTester
keeper testutil.TestKeeper
userAddrs []sdk.AccAddress
valAddrs []sdk.ValAddress
}
func TestEarnStakingRewardsIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(EarnAccumulatorStakingRewardsTestSuite))
}
func (suite *EarnAccumulatorStakingRewardsTestSuite) SetupTest() {
suite.IntegrationTester.SetupTest()
suite.keeper = testutil.TestKeeper{
Keeper: suite.App.GetIncentiveKeeper(),
}
_, addrs := app.GeneratePrivKeyAddressPairs(5)
suite.userAddrs = addrs[0:2]
suite.valAddrs = []sdk.ValAddress{
sdk.ValAddress(addrs[2]),
sdk.ValAddress(addrs[3]),
}
// Setup app with test state
authBuilder := app.NewAuthBankGenesisBuilder().
WithSimpleAccount(addrs[0], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[1], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[2], cs(c("ukava", 1e12))).
WithSimpleAccount(addrs[3], cs(c("ukava", 1e12)))
incentiveBuilder := testutil.NewIncentiveGenesisBuilder().
WithGenesisTime(suite.GenesisTime).
WithSimpleRewardPeriod(types.CLAIM_TYPE_EARN, "bkava", cs())
savingsBuilder := testutil.NewSavingsGenesisBuilder().
WithSupportedDenoms("bkava")
earnBuilder := testutil.NewEarnGenesisBuilder().
WithAllowedVaults(earntypes.AllowedVault{
Denom: "bkava",
Strategies: earntypes.StrategyTypes{earntypes.STRATEGY_TYPE_SAVINGS},
IsPrivateVault: false,
AllowedDepositors: nil,
})
stakingBuilder := testutil.NewStakingGenesisBuilder()
mintBuilder := testutil.NewMintGenesisBuilder().
WithInflationMax(sdk.OneDec()).
WithInflationMin(sdk.OneDec()).
WithMinter(sdk.OneDec(), sdk.ZeroDec()).
WithMintDenom("ukava")
suite.StartChainWithBuilders(
authBuilder,
incentiveBuilder,
savingsBuilder,
earnBuilder,
stakingBuilder,
mintBuilder,
)
}
func (suite *EarnAccumulatorStakingRewardsTestSuite) TestStakingRewardsDistributed() {
// derivative 1: 8 total staked, 7 to earn, 1 not in earn
// derivative 2: 2 total staked, 1 to earn, 1 not in earn
userMintAmount0 := c("ukava", 8e9)
userMintAmount1 := c("ukava", 2e9)
userDepositAmount0 := i(7e9)
userDepositAmount1 := i(1e9)
// Create two validators
derivative0, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[0], userMintAmount0)
suite.Require().NoError(err)
derivative1, err := suite.MintLiquidAnyValAddr(suite.userAddrs[0], suite.valAddrs[1], userMintAmount1)
suite.Require().NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], sdk.NewCoin(derivative0.Denom, userDepositAmount0), earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
err = suite.DeliverEarnMsgDeposit(suite.userAddrs[0], sdk.NewCoin(derivative1.Denom, userDepositAmount1), earntypes.STRATEGY_TYPE_SAVINGS)
suite.NoError(err)
// Get derivative denoms
lq := suite.App.GetLiquidKeeper()
vaultDenom1 := lq.GetLiquidStakingTokenDenom(suite.valAddrs[0])
vaultDenom2 := lq.GetLiquidStakingTokenDenom(suite.valAddrs[1])
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.Ctx = suite.Ctx.WithBlockTime(previousAccrualTime)
initialVault1RewardFactor := d("0.04")
initialVault2RewardFactor := d("0.04")
globalIndexes := types.MultiRewardIndexes{
{
CollateralType: vaultDenom1,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: initialVault1RewardFactor,
},
},
},
{
CollateralType: vaultDenom2,
RewardIndexes: types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: initialVault2RewardFactor,
},
},
},
}
suite.keeper.StoreGlobalIndexes(suite.Ctx, types.CLAIM_TYPE_EARN, globalIndexes)
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom1, suite.Ctx.BlockTime())
suite.keeper.Store.SetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom2, suite.Ctx.BlockTime())
val := suite.GetAbciValidator(suite.valAddrs[0])
// Mint tokens, distribute to validators, claim staking rewards
// 1 hour later
_, resBeginBlock := suite.NextBlockAfterWithReq(
1*time.Hour,
abci.RequestEndBlock{},
abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: true,
}},
},
},
)
// check time and factors
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, vaultDenom1, suite.Ctx.BlockTime())
suite.StoredTimeEquals(types.CLAIM_TYPE_EARN, vaultDenom2, suite.Ctx.BlockTime())
validatorRewards, _ := suite.GetBeginBlockClaimedStakingRewards(resBeginBlock)
suite.Require().Contains(validatorRewards, suite.valAddrs[0].String(), "there should be claim events for validator 1")
suite.Require().Contains(validatorRewards, suite.valAddrs[1].String(), "there should be claim events for validator 2")
// Total staking rewards / total source shares (**deposited in earn** not total minted)
// types.RewardIndexes.Quo() uses Dec.Quo() which uses bankers rounding.
// So we need to use Dec.Quo() to also round vs Dec.QuoInt() which truncates
expectedIndexes1 := validatorRewards[suite.valAddrs[0].String()].
AmountOf("ukava").
ToDec().
Quo(userDepositAmount0.ToDec())
expectedIndexes2 := validatorRewards[suite.valAddrs[1].String()].
AmountOf("ukava").
ToDec().
Quo(userDepositAmount1.ToDec())
// Only contains staking rewards
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, vaultDenom1, types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: initialVault1RewardFactor.Add(expectedIndexes1),
},
})
suite.StoredIndexesEqual(types.CLAIM_TYPE_EARN, vaultDenom2, types.RewardIndexes{
{
CollateralType: "ukava",
RewardFactor: initialVault2RewardFactor.Add(expectedIndexes2),
},
})
}

View File

@ -0,0 +1,50 @@
package accumulators_test
import (
"fmt"
"time"
"github.com/kava-labs/kava/x/incentive/keeper/accumulators"
"github.com/kava-labs/kava/x/incentive/types"
)
func (suite *AccumulateEarnRewardsIntegrationTests) TestEarnAccumulator_OnlyEarnClaimType() {
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
)
earnKeeper := suite.App.GetEarnKeeper()
for _, claimTypeValue := range types.ClaimType_value {
claimType := types.ClaimType(claimTypeValue)
if claimType == types.CLAIM_TYPE_EARN {
suite.NotPanics(func() {
err := accumulators.
NewEarnAccumulator(suite.keeper.Store, suite.App.GetLiquidKeeper(), &earnKeeper, suite.keeper.Adapters).
AccumulateRewards(suite.Ctx, claimType, period)
suite.NoError(err)
})
continue
}
suite.PanicsWithValue(
fmt.Sprintf(
"invalid claim type for earn accumulator, expected %s but got %s",
types.CLAIM_TYPE_EARN,
claimType,
),
func() {
err := accumulators.
NewEarnAccumulator(suite.keeper.Store, suite.App.GetLiquidKeeper(), &earnKeeper, suite.keeper.Adapters).
AccumulateRewards(suite.Ctx, claimType, period)
suite.NoError(err)
},
)
}
}

View File

@ -101,22 +101,22 @@ func (suite *EarnAdapterTestSuite) TestEarnAdapter_OwnerSharesBySource() {
},
))
suite.app.FundAccount(
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[0],
sdk.NewCoins(
sdk.NewCoin(vaultDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(vaultDenomB, sdk.NewInt(1000000000000)),
),
)
suite.app.FundAccount(
))
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[1],
sdk.NewCoins(
sdk.NewCoin(vaultDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(vaultDenomB, sdk.NewInt(1000000000000)),
),
)
))
err := earnKeeper.Deposit(
suite.ctx,
@ -235,22 +235,22 @@ func (suite *EarnAdapterTestSuite) TestEarnAdapter_TotalSharesBySource() {
},
))
suite.app.FundAccount(
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[0],
sdk.NewCoins(
sdk.NewCoin(vaultDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(vaultDenomB, sdk.NewInt(1000000000000)),
),
)
suite.app.FundAccount(
))
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[1],
sdk.NewCoins(
sdk.NewCoin(vaultDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(vaultDenomB, sdk.NewInt(1000000000000)),
),
)
))
err := earnKeeper.Deposit(
suite.ctx,

View File

@ -92,7 +92,7 @@ func (suite *SwapAdapterTestSuite) TestSwapAdapter_OwnerSharesBySource() {
sdk.ZeroDec(),
))
suite.app.FundAccount(
err := suite.app.FundAccount(
suite.ctx,
suite.addrs[0],
sdk.NewCoins(
@ -100,7 +100,9 @@ func (suite *SwapAdapterTestSuite) TestSwapAdapter_OwnerSharesBySource() {
sdk.NewCoin(poolDenomB, sdk.NewInt(1000000000000)),
),
)
suite.app.FundAccount(
suite.NoError(err)
err = suite.app.FundAccount(
suite.ctx,
suite.addrs[1],
sdk.NewCoins(
@ -108,8 +110,9 @@ func (suite *SwapAdapterTestSuite) TestSwapAdapter_OwnerSharesBySource() {
sdk.NewCoin(poolDenomB, sdk.NewInt(1000000000000)),
),
)
suite.NoError(err)
err := swapKeeper.Deposit(
err = swapKeeper.Deposit(
suite.ctx,
suite.addrs[0],
sdk.NewCoin(poolDenomA, sdk.NewInt(100)),
@ -219,22 +222,22 @@ func (suite *SwapAdapterTestSuite) TestSwapAdapter_TotalSharesBySource() {
sdk.ZeroDec(),
))
suite.app.FundAccount(
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[0],
sdk.NewCoins(
sdk.NewCoin(poolDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(poolDenomB, sdk.NewInt(1000000000000)),
),
)
suite.app.FundAccount(
))
suite.NoError(suite.app.FundAccount(
suite.ctx,
suite.addrs[1],
sdk.NewCoins(
sdk.NewCoin(poolDenomA, sdk.NewInt(1000000000000)),
sdk.NewCoin(poolDenomB, sdk.NewInt(1000000000000)),
),
)
))
err := swapKeeper.Deposit(
suite.ctx,

View File

@ -8,6 +8,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/keeper/adapters"
"github.com/kava-labs/kava/x/incentive/keeper/store"
"github.com/kava-labs/kava/x/incentive/types"
)
@ -26,7 +27,8 @@ type Keeper struct {
liquidKeeper types.LiquidKeeper
earnKeeper types.EarnKeeper
adapters adapters.SourceAdapters
Adapters adapters.SourceAdapters
Store store.IncentiveStore
// Keepers used for APY queries
mintKeeper types.MintKeeper
@ -59,10 +61,11 @@ func NewKeeper(
liquidKeeper: lqk,
earnKeeper: ek,
adapters: adapters.NewSourceAdapters(
Adapters: adapters.NewSourceAdapters(
swpk,
ek,
),
Store: store.NewIncentiveStore(cdc, key),
mintKeeper: mk,
distrKeeper: dk,
@ -890,277 +893,3 @@ func (k Keeper) IterateEarnRewardAccrualTimes(ctx sdk.Context, cb func(string, t
}
}
}
// -----------------------------------------------------------------------------
// New deduplicated methods
// GetClaim returns the claim in the store corresponding the the owner and
// claimType, and a boolean for if the claim was found
func (k Keeper) GetClaim(
ctx sdk.Context,
claimType types.ClaimType,
addr sdk.AccAddress,
) (types.Claim, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
bz := store.Get(addr)
if bz == nil {
return types.Claim{}, false
}
var c types.Claim
k.cdc.MustUnmarshal(bz, &c)
return c, true
}
// SetClaim sets the claim in the store corresponding to the owner and claimType
func (k Keeper) SetClaim(
ctx sdk.Context,
c types.Claim,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(c.Type))
bz := k.cdc.MustMarshal(&c)
store.Set(c.Owner, bz)
}
// DeleteClaim deletes the claim in the store corresponding to the owner and claimType
func (k Keeper) DeleteClaim(
ctx sdk.Context,
claimType types.ClaimType,
owner sdk.AccAddress,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
store.Delete(owner)
}
// IterateClaimsByClaimType iterates over all claim objects in the store of a given
// claimType and preforms a callback function
func (k Keeper) IterateClaimsByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(c types.Claim) (stop bool),
) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var c types.Claim
k.cdc.MustUnmarshal(iterator.Value(), &c)
if cb(c) {
break
}
}
}
// GetClaims returns all Claim objects in the store of a given claimType
func (k Keeper) GetClaims(
ctx sdk.Context,
claimType types.ClaimType,
) types.Claims {
var cs types.Claims
k.IterateClaimsByClaimType(ctx, claimType, func(c types.Claim) (stop bool) {
cs = append(cs, c)
return false
})
return cs
}
// IterateClaims iterates over all claim objects of any claimType in the
// store and preforms a callback function
func (k Keeper) IterateClaims(
ctx sdk.Context,
cb func(c types.Claim) (stop bool),
) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.ClaimKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var c types.Claim
k.cdc.MustUnmarshal(iterator.Value(), &c)
if cb(c) {
break
}
}
}
// GetAllClaims returns all Claim objects in the store of any claimType
func (k Keeper) GetAllClaims(ctx sdk.Context) types.Claims {
var cs types.Claims
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
cs = append(cs, c)
return false
})
return cs
}
// GetRewardAccrualTime fetches the last time rewards were accrued for the
// specified ClaimType and sourceID.
func (k Keeper) GetRewardAccrualTime(
ctx sdk.Context,
claimType types.ClaimType,
sourceID string,
) (time.Time, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
b := store.Get(types.GetKeyFromSourceID(sourceID))
if b == nil {
return time.Time{}, false
}
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(b, &accrualTime)
return accrualTime.PreviousAccumulationTime, true
}
// SetRewardAccrualTime stores the last time rewards were accrued for the
// specified ClaimType and sourceID.
func (k Keeper) SetRewardAccrualTime(
ctx sdk.Context,
claimType types.ClaimType,
sourceID string,
blockTime time.Time,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
at := types.NewAccrualTime(claimType, sourceID, blockTime)
bz := k.cdc.MustMarshal(&at)
store.Set(types.GetKeyFromSourceID(sourceID), bz)
}
// IterateRewardAccrualTimesByClaimType iterates over all reward accrual times of a given
// claimType and performs a callback function.
func (k Keeper) IterateRewardAccrualTimesByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(string, time.Time) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(iterator.Value(), &accrualTime)
if cb(accrualTime.CollateralType, accrualTime.PreviousAccumulationTime) {
break
}
}
}
// IterateRewardAccrualTimes iterates over all reward accrual times of any
// claimType and performs a callback function.
func (k Keeper) IterateRewardAccrualTimes(
ctx sdk.Context,
cb func(types.AccrualTime) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousRewardAccrualTimeKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(iterator.Value(), &accrualTime)
if cb(accrualTime) {
break
}
}
}
// GetAllRewardAccrualTimes returns all reward accrual times of any claimType.
func (k Keeper) GetAllRewardAccrualTimes(ctx sdk.Context) types.AccrualTimes {
var ats types.AccrualTimes
k.IterateRewardAccrualTimes(
ctx,
func(accrualTime types.AccrualTime) bool {
ats = append(ats, accrualTime)
return false
},
)
return ats
}
// SetRewardIndexes stores the global reward indexes that track total rewards of
// a given claim type and collateralType.
func (k Keeper) SetRewardIndexes(
ctx sdk.Context,
claimType types.ClaimType,
collateralType string,
indexes types.RewardIndexes,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
bz := k.cdc.MustMarshal(&types.TypedRewardIndexes{
ClaimType: claimType,
CollateralType: collateralType,
RewardIndexes: indexes,
})
store.Set(types.GetKeyFromSourceID(collateralType), bz)
}
// GetRewardIndexesOfClaimType fetches the global reward indexes that track total rewards
// of a given claimType and collateralType.
func (k Keeper) GetRewardIndexesOfClaimType(
ctx sdk.Context,
claimType types.ClaimType,
collateralType string,
) (types.RewardIndexes, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
bz := store.Get(types.GetKeyFromSourceID(collateralType))
if bz == nil {
return types.RewardIndexes{}, false
}
var proto types.TypedRewardIndexes
k.cdc.MustUnmarshal(bz, &proto)
return proto.RewardIndexes, true
}
// IterateRewardIndexesByClaimType iterates over all reward index objects in the store of a
// given ClaimType and performs a callback function.
func (k Keeper) IterateRewardIndexesByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(types.TypedRewardIndexes) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var typedRewardIndexes types.TypedRewardIndexes
k.cdc.MustUnmarshal(iterator.Value(), &typedRewardIndexes)
if cb(typedRewardIndexes) {
break
}
}
}
// IterateRewardIndexes iterates over all reward index objects in the store
// of all ClaimTypes and performs a callback function.
func (k Keeper) IterateRewardIndexes(
ctx sdk.Context,
cb func(types.TypedRewardIndexes) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardIndexesKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var typedRewardIndexes types.TypedRewardIndexes
k.cdc.MustUnmarshal(iterator.Value(), &typedRewardIndexes)
if cb(typedRewardIndexes) {
break
}
}
}
// GetRewardIndexes returns all reward indexes of any claimType.
func (k Keeper) GetRewardIndexes(ctx sdk.Context) types.TypedRewardIndexesList {
var tril types.TypedRewardIndexesList
k.IterateRewardIndexes(
ctx,
func(typedRewardIndexes types.TypedRewardIndexes) bool {
tril = append(tril, typedRewardIndexes)
return false
},
)
return tril
}

View File

@ -20,13 +20,13 @@ func (suite *KeeperTestSuite) TestGetSetDeleteClaims() {
nonEmptyMultiRewardIndexes,
)
_, found := suite.keeper.GetClaim(suite.ctx, claimType, suite.addrs[0])
_, found := suite.keeper.Store.GetClaim(suite.ctx, claimType, suite.addrs[0])
suite.Require().False(found)
suite.Require().NotPanics(func() {
suite.keeper.SetClaim(suite.ctx, c)
suite.keeper.Store.SetClaim(suite.ctx, c)
})
testC, found := suite.keeper.GetClaim(suite.ctx, claimType, suite.addrs[0])
testC, found := suite.keeper.Store.GetClaim(suite.ctx, claimType, suite.addrs[0])
suite.Require().True(found)
suite.Require().Equal(c, testC)
@ -38,14 +38,14 @@ func (suite *KeeperTestSuite) TestGetSetDeleteClaims() {
}
otherClaimType := types.ClaimType(otherClaimTypeValue)
_, found := suite.keeper.GetClaim(suite.ctx, otherClaimType, suite.addrs[0])
_, found := suite.keeper.Store.GetClaim(suite.ctx, otherClaimType, suite.addrs[0])
suite.Require().False(found, "claim type %s should not exist", otherClaimTypeName)
}
suite.Require().NotPanics(func() {
suite.keeper.DeleteClaim(suite.ctx, claimType, suite.addrs[0])
suite.keeper.Store.DeleteClaim(suite.ctx, claimType, suite.addrs[0])
})
_, found = suite.keeper.GetClaim(suite.ctx, claimType, suite.addrs[0])
_, found = suite.keeper.Store.GetClaim(suite.ctx, claimType, suite.addrs[0])
suite.Require().False(found)
})
}
@ -65,14 +65,14 @@ func (suite *KeeperTestSuite) TestIterateClaims() {
}
for _, claim := range claims {
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
}
for _, claimTypeValue := range types.ClaimType_value {
claimType := types.ClaimType(claimTypeValue)
// Claims of specific claim type only should be returned
claims := suite.keeper.GetClaims(suite.ctx, claimType)
claims := suite.keeper.Store.GetClaims(suite.ctx, claimType)
suite.Require().Len(claims, 2)
suite.Require().Equalf(
claims, types.Claims{
@ -83,7 +83,7 @@ func (suite *KeeperTestSuite) TestIterateClaims() {
)
}
allClaims := suite.keeper.GetAllClaims(suite.ctx)
allClaims := suite.keeper.Store.GetAllClaims(suite.ctx)
suite.Require().Len(allClaims, len(claims))
suite.Require().ElementsMatch(allClaims, claims, "GetAllClaims() should return claims of all types")
}
@ -111,11 +111,11 @@ func (suite *KeeperTestSuite) TestGetSetRewardAccrualTimes() {
suite.Run(tc.name, func() {
suite.SetupApp()
_, found := suite.keeper.GetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey)
_, found := suite.keeper.Store.GetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey)
suite.False(found)
setFunc := func() {
suite.keeper.SetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey, tc.accrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey, tc.accrualTime)
}
if tc.panics {
suite.Panics(setFunc)
@ -131,11 +131,11 @@ func (suite *KeeperTestSuite) TestGetSetRewardAccrualTimes() {
continue
}
_, found := suite.keeper.GetRewardAccrualTime(suite.ctx, claimType, tc.subKey)
_, found := suite.keeper.Store.GetRewardAccrualTime(suite.ctx, claimType, tc.subKey)
suite.False(found, "reward accrual time for claim type %s should not exist", claimType)
}
storedTime, found := suite.keeper.GetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey)
storedTime, found := suite.keeper.Store.GetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, tc.subKey)
suite.True(found)
suite.Equal(tc.accrualTime, storedTime)
})
@ -213,11 +213,11 @@ func (suite *KeeperTestSuite) TestGetSetRewardIndexes() {
suite.Run(tc.name, func() {
suite.SetupApp()
_, found := suite.keeper.GetRewardIndexesOfClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType)
_, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType)
suite.False(found)
setFunc := func() {
suite.keeper.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType, tc.indexes)
suite.keeper.Store.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType, tc.indexes)
}
if tc.panics {
suite.Panics(setFunc)
@ -226,7 +226,7 @@ func (suite *KeeperTestSuite) TestGetSetRewardIndexes() {
suite.NotPanics(setFunc)
}
storedIndexes, found := suite.keeper.GetRewardIndexesOfClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType)
storedIndexes, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, tc.collateralType)
suite.True(found)
suite.Equal(tc.wantIndex, storedIndexes)
@ -239,7 +239,7 @@ func (suite *KeeperTestSuite) TestGetSetRewardIndexes() {
otherClaimType := types.ClaimType(otherClaimTypeValue)
// Other claim types should not be affected
_, found := suite.keeper.GetRewardIndexesOfClaimType(suite.ctx, otherClaimType, tc.collateralType)
_, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.ctx, otherClaimType, tc.collateralType)
suite.False(found)
}
})
@ -252,11 +252,11 @@ func (suite *KeeperTestSuite) TestIterateRewardAccrualTimes() {
expectedAccrualTimes := nonEmptyAccrualTimes
for _, at := range expectedAccrualTimes {
suite.keeper.SetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, at.denom, at.time)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, at.denom, at.time)
}
var actualAccrualTimes []accrualtime
suite.keeper.IterateRewardAccrualTimesByClaimType(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, func(denom string, accrualTime time.Time) bool {
suite.keeper.Store.IterateRewardAccrualTimesByClaimType(suite.ctx, types.CLAIM_TYPE_USDX_MINTING, func(denom string, accrualTime time.Time) bool {
actualAccrualTimes = append(actualAccrualTimes, accrualtime{denom: denom, time: accrualTime})
return false
})
@ -278,7 +278,7 @@ func (suite *KeeperTestSuite) TestIterateAllRewardAccrualTimes() {
}
for _, at := range nonEmptyAccrualTimes {
suite.keeper.SetRewardAccrualTime(suite.ctx, claimType, at.denom, at.time)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, at.denom, at.time)
expectedAccrualTimes = append(expectedAccrualTimes, types.NewAccrualTime(
claimType,
@ -290,7 +290,7 @@ func (suite *KeeperTestSuite) TestIterateAllRewardAccrualTimes() {
}
var actualAccrualTimes types.AccrualTimes
suite.keeper.IterateRewardAccrualTimes(
suite.keeper.Store.IterateRewardAccrualTimes(
suite.ctx,
func(accrualTime types.AccrualTime) bool {
actualAccrualTimes = append(actualAccrualTimes, accrualTime)
@ -354,16 +354,16 @@ func (suite *KeeperTestSuite) TestIterateRewardIndexes() {
}
for _, mi := range swapMultiIndexes {
suite.keeper.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_SWAP, mi.CollateralType, mi.RewardIndexes)
suite.keeper.Store.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_SWAP, mi.CollateralType, mi.RewardIndexes)
}
for _, mi := range earnMultiIndexes {
// These should be excluded when iterating over swap indexes
suite.keeper.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_EARN, mi.CollateralType, mi.RewardIndexes)
suite.keeper.Store.SetRewardIndexes(suite.ctx, types.CLAIM_TYPE_EARN, mi.CollateralType, mi.RewardIndexes)
}
actualMultiIndexesMap := make(map[types.ClaimType]types.MultiRewardIndexes)
suite.keeper.IterateRewardIndexesByClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, func(rewardIndex types.TypedRewardIndexes) bool {
suite.keeper.Store.IterateRewardIndexesByClaimType(suite.ctx, types.CLAIM_TYPE_SWAP, func(rewardIndex types.TypedRewardIndexes) bool {
actualMultiIndexesMap[rewardIndex.ClaimType] = actualMultiIndexesMap[rewardIndex.ClaimType].With(rewardIndex.CollateralType, rewardIndex.RewardIndexes)
return false
})
@ -407,12 +407,12 @@ func (suite *KeeperTestSuite) TestIterateAllRewardIndexes() {
claimType := types.ClaimType(claimTypeValue)
for _, mi := range multiIndexes {
suite.keeper.SetRewardIndexes(suite.ctx, claimType, mi.CollateralType, mi.RewardIndexes)
suite.keeper.Store.SetRewardIndexes(suite.ctx, claimType, mi.CollateralType, mi.RewardIndexes)
}
}
actualMultiIndexesMap := make(map[types.ClaimType]types.MultiRewardIndexes)
suite.keeper.IterateRewardIndexes(suite.ctx, func(rewardIndex types.TypedRewardIndexes) bool {
suite.keeper.Store.IterateRewardIndexes(suite.ctx, func(rewardIndex types.TypedRewardIndexes) bool {
actualMultiIndexesMap[rewardIndex.ClaimType] = actualMultiIndexesMap[rewardIndex.ClaimType].With(rewardIndex.CollateralType, rewardIndex.RewardIndexes)
return false
})

View File

@ -4,6 +4,7 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/keeper/accumulators"
"github.com/kava-labs/kava/x/incentive/types"
)
@ -13,28 +14,17 @@ func (k Keeper) AccumulateRewards(
ctx sdk.Context,
claimType types.ClaimType,
rewardPeriod types.MultiRewardPeriod,
) {
previousAccrualTime, found := k.GetRewardAccrualTime(ctx, claimType, rewardPeriod.CollateralType)
if !found {
previousAccrualTime = ctx.BlockTime()
) error {
var accumulator types.RewardAccumulator
switch claimType {
case types.CLAIM_TYPE_EARN:
accumulator = accumulators.NewEarnAccumulator(k.Store, k.liquidKeeper, k.earnKeeper, k.Adapters)
default:
accumulator = accumulators.NewBasicAccumulator(k.Store, k.Adapters)
}
indexes, found := k.GetRewardIndexesOfClaimType(ctx, claimType, rewardPeriod.CollateralType)
if !found {
indexes = types.RewardIndexes{}
}
acc := types.NewAccumulator(previousAccrualTime, indexes)
totalSource := k.adapters.TotalSharesBySource(ctx, claimType, rewardPeriod.CollateralType)
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
k.SetRewardAccrualTime(ctx, claimType, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
if len(acc.Indexes) > 0 {
// the store panics when setting empty or nil indexes
k.SetRewardIndexes(ctx, claimType, rewardPeriod.CollateralType, acc.Indexes)
}
return accumulator.AccumulateRewards(ctx, claimType, rewardPeriod)
}
// InitializeClaim creates a new claim with zero rewards and indexes matching
@ -45,18 +35,18 @@ func (k Keeper) InitializeClaim(
sourceID string,
owner sdk.AccAddress,
) {
claim, found := k.GetClaim(ctx, claimType, owner)
claim, found := k.Store.GetClaim(ctx, claimType, owner)
if !found {
claim = types.NewClaim(claimType, owner, sdk.Coins{}, nil)
}
globalRewardIndexes, found := k.GetRewardIndexesOfClaimType(ctx, claimType, sourceID)
globalRewardIndexes, found := k.Store.GetRewardIndexesOfClaimType(ctx, claimType, sourceID)
if !found {
globalRewardIndexes = types.RewardIndexes{}
}
claim.RewardIndexes = claim.RewardIndexes.With(sourceID, globalRewardIndexes)
k.SetClaim(ctx, claim)
k.Store.SetClaim(ctx, claim)
}
// SynchronizeClaim updates the claim object by adding any accumulated rewards
@ -68,13 +58,13 @@ func (k Keeper) SynchronizeClaim(
owner sdk.AccAddress,
shares sdk.Dec,
) {
claim, found := k.GetClaim(ctx, claimType, owner)
claim, found := k.Store.GetClaim(ctx, claimType, owner)
if !found {
return
}
claim = k.synchronizeClaim(ctx, claim, sourceID, owner, shares)
k.SetClaim(ctx, claim)
k.Store.SetClaim(ctx, claim)
}
// synchronizeClaim updates the reward and indexes in a claim for one sourceID.
@ -85,7 +75,7 @@ func (k *Keeper) synchronizeClaim(
owner sdk.AccAddress,
shares sdk.Dec,
) types.Claim {
globalRewardIndexes, found := k.GetRewardIndexesOfClaimType(ctx, claim.Type, sourceID)
globalRewardIndexes, found := k.Store.GetRewardIndexesOfClaimType(ctx, claim.Type, sourceID)
if !found {
// The global factor is only not found if
// - the pool has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
@ -124,19 +114,19 @@ func (k Keeper) GetSynchronizedClaim(
claimType types.ClaimType,
owner sdk.AccAddress,
) (types.Claim, bool) {
claim, found := k.GetClaim(ctx, claimType, owner)
claim, found := k.Store.GetClaim(ctx, claimType, owner)
if !found {
return types.Claim{}, false
}
// Fetch all source IDs from indexes
var sourceIDs []string
k.IterateRewardIndexesByClaimType(ctx, claimType, func(rewardIndexes types.TypedRewardIndexes) bool {
k.Store.IterateRewardIndexesByClaimType(ctx, claimType, func(rewardIndexes types.TypedRewardIndexes) bool {
sourceIDs = append(sourceIDs, rewardIndexes.CollateralType)
return false
})
accShares := k.adapters.OwnerSharesBySource(ctx, claimType, owner, sourceIDs)
accShares := k.Adapters.OwnerSharesBySource(ctx, claimType, owner, sourceIDs)
// Synchronize claim for each source ID
for _, share := range accShares {

View File

@ -21,7 +21,7 @@ func (suite *AccumulateTestSuite) storedTimeEquals(
poolID string,
expected time.Time,
) {
storedTime, found := suite.keeper.GetRewardAccrualTime(suite.ctx, claimType, poolID)
storedTime, found := suite.keeper.Store.GetRewardAccrualTime(suite.ctx, claimType, poolID)
suite.True(found)
suite.Equal(expected, storedTime)
}
@ -31,7 +31,7 @@ func (suite *AccumulateTestSuite) storedIndexesEquals(
poolID string,
expected types.RewardIndexes,
) {
storedIndexes, found := suite.keeper.GetRewardIndexesOfClaimType(suite.ctx, claimType, poolID)
storedIndexes, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.ctx, claimType, poolID)
suite.Equal(found, expected != nil)
if found {
suite.Equal(expected, storedIndexes)
@ -63,7 +63,7 @@ func (suite *AccumulateTestSuite) TestStateUpdatedWhenBlockTimeHasIncreased() {
},
})
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
@ -117,7 +117,7 @@ func (suite *AccumulateTestSuite) TestStateUnchangedWhenBlockTimeHasNotIncreased
}
suite.storeGlobalIndexes(claimType, previousIndexes)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
@ -163,7 +163,7 @@ func (suite *AccumulateTestSuite) TestNoAccumulationWhenSourceSharesAreZero() {
}
suite.storeGlobalIndexes(claimType, previousIndexes)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
@ -283,7 +283,7 @@ func (suite *AccumulateTestSuite) TestNoAccumulationWhenBeforeStartTime() {
}
suite.storeGlobalIndexes(claimType, previousIndexes)
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
suite.keeper.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
@ -314,7 +314,7 @@ func (suite *AccumulateTestSuite) TestPanicWhenCurrentTimeLessThanPrevious() {
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.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
suite.keeper.Store.SetRewardAccrualTime(suite.ctx, claimType, pool, previousAccrualTime)
firstAccrualTime := time.Time{}

View File

@ -9,6 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/keeper/accumulators"
"github.com/kava-labs/kava/x/incentive/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
@ -32,34 +33,6 @@ func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiR
return nil
}
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) error {
@ -112,7 +85,7 @@ func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.M
bkavaDenom,
rewardPeriod.Start,
rewardPeriod.End,
GetProportionalRewardsPerSecond(
accumulators.GetProportionalRewardsPerSecond(
rewardPeriod,
totalBkavaValue.Amount,
derivativeValue.Amount,

View File

@ -383,7 +383,8 @@ func (suite *AccumulateEarnRewardsIntegrationTests) TestStateUnchangedWhenBlockT
// Must manually accumulate rewards as BeginBlockers only run when the block time increases
// This does not run any x/mint or x/distribution BeginBlockers
suite.keeper.AccumulateEarnRewards(suite.Ctx, period)
err = suite.keeper.AccumulateEarnRewards(suite.Ctx, period)
suite.NoError(err)
// check time and factors
@ -646,7 +647,8 @@ func (suite *AccumulateEarnRewardsIntegrationTests) TestNoPanicWhenStateDoesNotE
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)
err := suite.keeper.AccumulateEarnRewards(suite.Ctx, period)
suite.NoError(err)
})
// Times are not stored for vaults with no state

View File

@ -37,7 +37,7 @@ func (suite *InitializeClaimTests) TestClaimAddedWhenClaimDoesNotExistAndNoRewar
suite.keeper.InitializeClaim(suite.ctx, claimType, collateralType, owner)
syncedClaim, found := suite.keeper.GetClaim(suite.ctx, claimType, owner)
syncedClaim, found := suite.keeper.Store.GetClaim(suite.ctx, claimType, owner)
suite.True(found)
// A new claim should have empty indexes. It doesn't strictly need the collateralType either.
expectedIndexes := types.MultiRewardIndexes{{
@ -72,7 +72,7 @@ func (suite *InitializeClaimTests) TestClaimAddedWhenClaimDoesNotExistAndRewards
suite.keeper.InitializeClaim(suite.ctx, claimType, collateralType, owner)
syncedClaim, found := suite.keeper.GetClaim(suite.ctx, claimType, owner)
syncedClaim, found := suite.keeper.Store.GetClaim(suite.ctx, claimType, owner)
suite.True(found)
// a new claim should start with the current global indexes
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
@ -107,13 +107,13 @@ func (suite *InitializeClaimTests) TestClaimUpdatedWhenClaimExistsAndNoRewards()
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
// no global indexes stored as the new pool is not rewarded
suite.keeper.InitializeClaim(suite.ctx, claimType, newCollateralType, claim.Owner)
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// The preexisting indexes shouldn't be changed. It doesn't strictly need the new collateralType either.
expectedIndexes := types.MultiRewardIndexes{
{
@ -163,7 +163,7 @@ func (suite *InitializeClaimTests) TestClaimUpdatedWhenClaimExistsAndRewardsExis
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -179,7 +179,7 @@ func (suite *InitializeClaimTests) TestClaimUpdatedWhenClaimExistsAndRewardsExis
suite.keeper.InitializeClaim(suite.ctx, claimType, newCollateralType, claim.Owner)
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// only the indexes for the new pool should be updated
expectedIndexes := types.MultiRewardIndexes{
{

View File

@ -51,7 +51,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenGlobalIndexesHaveIncreas
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -70,7 +70,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenGlobalIndexesHaveIncreas
suite.keeper.SynchronizeClaim(suite.ctx, claimType, collateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// indexes updated from global
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
// new reward is (new index - old index) * user shares
@ -104,7 +104,7 @@ func (suite *SynchronizeClaimTests) TestClaimUnchangedWhenGlobalIndexesUnchanged
Reward: arbitraryCoins(),
RewardIndexes: unchangingIndexes,
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
suite.storeGlobalIndexes(claimType, unchangingIndexes)
@ -112,7 +112,7 @@ func (suite *SynchronizeClaimTests) TestClaimUnchangedWhenGlobalIndexesUnchanged
suite.keeper.SynchronizeClaim(suite.ctx, claimType, collateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// claim should have the same rewards and indexes as before
suite.Equal(claim, syncedClaim)
}
@ -141,7 +141,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenNewRewardAdded() {
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -171,7 +171,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenNewRewardAdded() {
suite.keeper.SynchronizeClaim(suite.ctx, claimType, newlyRewardcollateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// the new indexes should be added to the claim, but the old ones should be unchanged
newlyRewrdedIndexes, _ := globalIndexes.Get(newlyRewardcollateralType)
expectedIndexes := claim.RewardIndexes.With(newlyRewardcollateralType, newlyRewrdedIndexes)
@ -197,7 +197,7 @@ func (suite *SynchronizeClaimTests) TestClaimUnchangedWhenNoReward() {
Reward: arbitraryCoins(),
RewardIndexes: nonEmptyMultiRewardIndexes,
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
// No global indexes stored as this pool is not rewarded
@ -205,7 +205,7 @@ func (suite *SynchronizeClaimTests) TestClaimUnchangedWhenNoReward() {
suite.keeper.SynchronizeClaim(suite.ctx, claimType, collateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
suite.Equal(claim, syncedClaim)
}
@ -233,7 +233,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenNewRewardDenomAdded() {
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -258,7 +258,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenNewRewardDenomAdded() {
suite.keeper.SynchronizeClaim(suite.ctx, claimType, collateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// indexes should have the new reward denom added
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
// new reward is (new index - old index) * shares
@ -293,7 +293,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenGlobalIndexesIncreasedAn
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -312,7 +312,7 @@ func (suite *SynchronizeClaimTests) TestClaimUpdatedWhenGlobalIndexesIncreasedAn
suite.keeper.SynchronizeClaim(suite.ctx, claimType, collateralType, claim.Owner, userShares.ToDec())
syncedClaim, _ := suite.keeper.GetClaim(suite.ctx, claimType, claim.Owner)
syncedClaim, _ := suite.keeper.Store.GetClaim(suite.ctx, claimType, claim.Owner)
// indexes updated from global
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
// reward is unchanged
@ -339,7 +339,7 @@ func (suite *SynchronizeClaimTests) TestGetSyncedClaim_ClaimUnchangedWhenNoGloba
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
// no global indexes for any pool
@ -377,7 +377,7 @@ func (suite *SynchronizeClaimTests) TestGetSyncedClaim_ClaimUpdatedWhenMissingIn
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{
@ -437,7 +437,7 @@ func (suite *SynchronizeClaimTests) TestGetSyncedClaim_ClaimUpdatedWhenMissingIn
},
},
}
suite.keeper.SetClaim(suite.ctx, claim)
suite.keeper.Store.SetClaim(suite.ctx, claim)
globalIndexes := types.MultiRewardIndexes{
{

View File

@ -0,0 +1,95 @@
package store
import (
"time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/types"
)
// GetRewardAccrualTime fetches the last time rewards were accrued for the
// specified ClaimType and sourceID.
func (k IncentiveStore) GetRewardAccrualTime(
ctx sdk.Context,
claimType types.ClaimType,
sourceID string,
) (time.Time, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
b := store.Get(types.GetKeyFromSourceID(sourceID))
if b == nil {
return time.Time{}, false
}
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(b, &accrualTime)
return accrualTime.PreviousAccumulationTime, true
}
// SetRewardAccrualTime stores the last time rewards were accrued for the
// specified ClaimType and sourceID.
func (k IncentiveStore) SetRewardAccrualTime(
ctx sdk.Context,
claimType types.ClaimType,
sourceID string,
blockTime time.Time,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
at := types.NewAccrualTime(claimType, sourceID, blockTime)
bz := k.cdc.MustMarshal(&at)
store.Set(types.GetKeyFromSourceID(sourceID), bz)
}
// IterateRewardAccrualTimesByClaimType iterates over all reward accrual times of a given
// claimType and performs a callback function.
func (k IncentiveStore) IterateRewardAccrualTimesByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(string, time.Time) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(iterator.Value(), &accrualTime)
if cb(accrualTime.CollateralType, accrualTime.PreviousAccumulationTime) {
break
}
}
}
// IterateRewardAccrualTimes iterates over all reward accrual times of any
// claimType and performs a callback function.
func (k IncentiveStore) IterateRewardAccrualTimes(
ctx sdk.Context,
cb func(types.AccrualTime) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousRewardAccrualTimeKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var accrualTime types.AccrualTime
k.cdc.MustUnmarshal(iterator.Value(), &accrualTime)
if cb(accrualTime) {
break
}
}
}
// GetAllRewardAccrualTimes returns all reward accrual times of any claimType.
func (k IncentiveStore) GetAllRewardAccrualTimes(ctx sdk.Context) types.AccrualTimes {
var ats types.AccrualTimes
k.IterateRewardAccrualTimes(
ctx,
func(accrualTime types.AccrualTime) bool {
ats = append(ats, accrualTime)
return false
},
)
return ats
}

View File

@ -0,0 +1,105 @@
package store
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/kava-labs/kava/x/incentive/types"
)
// GetClaim returns the claim in the store corresponding the the owner and
// claimType, and a boolean for if the claim was found
func (k IncentiveStore) GetClaim(
ctx sdk.Context,
claimType types.ClaimType,
addr sdk.AccAddress,
) (types.Claim, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
bz := store.Get(addr)
if bz == nil {
return types.Claim{}, false
}
var c types.Claim
k.cdc.MustUnmarshal(bz, &c)
return c, true
}
// SetClaim sets the claim in the store corresponding to the owner and claimType
func (k IncentiveStore) SetClaim(
ctx sdk.Context,
c types.Claim,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(c.Type))
bz := k.cdc.MustMarshal(&c)
store.Set(c.Owner, bz)
}
// DeleteClaim deletes the claim in the store corresponding to the owner and claimType
func (k IncentiveStore) DeleteClaim(
ctx sdk.Context,
claimType types.ClaimType,
owner sdk.AccAddress,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
store.Delete(owner)
}
// IterateClaimsByClaimType iterates over all claim objects in the store of a given
// claimType and preforms a callback function
func (k IncentiveStore) IterateClaimsByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(c types.Claim) (stop bool),
) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.GetClaimKeyPrefix(claimType))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var c types.Claim
k.cdc.MustUnmarshal(iterator.Value(), &c)
if cb(c) {
break
}
}
}
// GetClaims returns all Claim objects in the store of a given claimType
func (k IncentiveStore) GetClaims(
ctx sdk.Context,
claimType types.ClaimType,
) types.Claims {
var cs types.Claims
k.IterateClaimsByClaimType(ctx, claimType, func(c types.Claim) (stop bool) {
cs = append(cs, c)
return false
})
return cs
}
// IterateClaims iterates over all claim objects of any claimType in the
// store and preforms a callback function
func (k IncentiveStore) IterateClaims(
ctx sdk.Context,
cb func(c types.Claim) (stop bool),
) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.ClaimKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var c types.Claim
k.cdc.MustUnmarshal(iterator.Value(), &c)
if cb(c) {
break
}
}
}
// GetAllClaims returns all Claim objects in the store of any claimType
func (k IncentiveStore) GetAllClaims(ctx sdk.Context) types.Claims {
var cs types.Claims
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
cs = append(cs, c)
return false
})
return cs
}

View File

@ -0,0 +1,95 @@
package store
import (
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/types"
)
// SetRewardIndexes stores the global reward indexes that track total rewards of
// a given claim type and collateralType.
func (k IncentiveStore) SetRewardIndexes(
ctx sdk.Context,
claimType types.ClaimType,
collateralType string,
indexes types.RewardIndexes,
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
bz := k.cdc.MustMarshal(&types.TypedRewardIndexes{
ClaimType: claimType,
CollateralType: collateralType,
RewardIndexes: indexes,
})
store.Set(types.GetKeyFromSourceID(collateralType), bz)
}
// GetRewardIndexesOfClaimType fetches the global reward indexes that track total rewards
// of a given claimType and collateralType.
func (k IncentiveStore) GetRewardIndexesOfClaimType(
ctx sdk.Context,
claimType types.ClaimType,
collateralType string,
) (types.RewardIndexes, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
bz := store.Get(types.GetKeyFromSourceID(collateralType))
if bz == nil {
return types.RewardIndexes{}, false
}
var proto types.TypedRewardIndexes
k.cdc.MustUnmarshal(bz, &proto)
return proto.RewardIndexes, true
}
// IterateRewardIndexesByClaimType iterates over all reward index objects in the store of a
// given ClaimType and performs a callback function.
func (k IncentiveStore) IterateRewardIndexesByClaimType(
ctx sdk.Context,
claimType types.ClaimType,
cb func(types.TypedRewardIndexes) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.GetRewardIndexesKeyPrefix(claimType))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var typedRewardIndexes types.TypedRewardIndexes
k.cdc.MustUnmarshal(iterator.Value(), &typedRewardIndexes)
if cb(typedRewardIndexes) {
break
}
}
}
// IterateRewardIndexes iterates over all reward index objects in the store
// of all ClaimTypes and performs a callback function.
func (k IncentiveStore) IterateRewardIndexes(
ctx sdk.Context,
cb func(types.TypedRewardIndexes) (stop bool),
) {
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardIndexesKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var typedRewardIndexes types.TypedRewardIndexes
k.cdc.MustUnmarshal(iterator.Value(), &typedRewardIndexes)
if cb(typedRewardIndexes) {
break
}
}
}
// GetRewardIndexes returns all reward indexes of any claimType.
func (k IncentiveStore) GetRewardIndexes(ctx sdk.Context) types.TypedRewardIndexesList {
var tril types.TypedRewardIndexesList
k.IterateRewardIndexes(
ctx,
func(typedRewardIndexes types.TypedRewardIndexes) bool {
tril = append(tril, typedRewardIndexes)
return false
},
)
return tril
}

View File

@ -0,0 +1,20 @@
package store
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// IncentiveStore provides methods for interacting with the incentive store.
type IncentiveStore struct {
cdc codec.Codec
key sdk.StoreKey
}
// NewIncentiveStore creates a new IncentiveStore
func NewIncentiveStore(cdc codec.Codec, key sdk.StoreKey) IncentiveStore {
return IncentiveStore{
cdc: cdc,
key: key,
}
}

View File

@ -121,7 +121,7 @@ func (suite *unitTester) storeGlobalEarnIndexes(indexes types.MultiRewardIndexes
func (suite *unitTester) storeGlobalIndexes(claimType types.ClaimType, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
suite.keeper.SetRewardIndexes(suite.ctx, claimType, i.CollateralType, i.RewardIndexes)
suite.keeper.Store.SetRewardIndexes(suite.ctx, claimType, i.CollateralType, i.RewardIndexes)
}
}

View File

@ -1,6 +1,7 @@
package testutil
import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/codec"
@ -191,6 +192,48 @@ func (builder IncentiveGenesisBuilder) WithSimpleEarnRewardPeriod(ctype string,
return builder.WithInitializedEarnRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
}
// WithInitializedRewardPeriod adds an initialized typed reward period.
func (builder IncentiveGenesisBuilder) WithInitializedRewardPeriod(
claimType types.ClaimType,
periods types.MultiRewardPeriods,
) IncentiveGenesisBuilder {
// Append to claim type if it exists -- claim types must be unique in RewardPeriods field
for _, rewardPeriod := range builder.Params.RewardPeriods {
if rewardPeriod.ClaimType == claimType {
rewardPeriod.RewardPeriods = append(rewardPeriod.RewardPeriods, periods...)
return builder
}
}
// Add new reward period for claim type
builder.Params.RewardPeriods = append(builder.Params.RewardPeriods, types.NewTypedMultiRewardPeriod(claimType, periods))
for _, period := range periods {
accumulationTimeForPeriod := types.NewAccrualTime(claimType, period.CollateralType, builder.genesisTime)
builder.AccrualTimes = append(
builder.AccrualTimes,
accumulationTimeForPeriod,
)
if err := builder.AccrualTimes.Validate(); err != nil {
panic(fmt.Errorf("invalid accrual times: %w", err))
}
}
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleRewardPeriod(
claimType types.ClaimType,
collateralType string,
rewardsPerSecond sdk.Coins,
) IncentiveGenesisBuilder {
return builder.WithInitializedRewardPeriod(
claimType,
types.MultiRewardPeriods{builder.simpleRewardPeriod(collateralType, rewardsPerSecond)},
)
}
func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.MultipliersPerDenoms) IncentiveGenesisBuilder {
builder.Params.ClaimMultipliers = multipliers

View File

@ -442,7 +442,8 @@ func (suite *IntegrationTester) AddTestAddrsFromPubKeys(ctx sdk.Context, pubKeys
initCoins := sdk.NewCoins(sdk.NewCoin(suite.App.GetStakingKeeper().BondDenom(ctx), accAmt))
for _, pk := range pubKeys {
suite.App.FundAccount(ctx, sdk.AccAddress(pk.Address()), initCoins)
err := suite.App.FundAccount(ctx, sdk.AccAddress(pk.Address()), initCoins)
suite.Require().NoError(err)
}
}
@ -468,6 +469,28 @@ func (suite *IntegrationTester) StoredEarnIndexesEqual(denom string, expected ty
}
}
func (suite *IntegrationTester) StoredTimeEquals(claimType types.ClaimType, denom string, expected time.Time) {
storedTime, found := suite.App.GetIncentiveKeeper().Store.GetRewardAccrualTime(suite.Ctx, claimType, denom)
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 *IntegrationTester) StoredIndexesEqual(claimType types.ClaimType, denom string, expected types.RewardIndexes) {
storedIndexes, found := suite.App.GetIncentiveKeeper().Store.GetRewardIndexesOfClaimType(suite.Ctx, claimType, denom)
suite.Equal(found, expected != nil)
if found {
suite.Equal(expected, storedIndexes)
} else {
// Can't compare Equal for types.RewardIndexes(nil) vs types.RewardIndexes{}
suite.Empty(storedIndexes)
}
}
func (suite *IntegrationTester) AddIncentiveEarnMultiRewardPeriod(period types.MultiRewardPeriod) {
ik := suite.App.GetIncentiveKeeper()
params := ik.GetParams(suite.Ctx)
@ -489,6 +512,44 @@ func (suite *IntegrationTester) AddIncentiveEarnMultiRewardPeriod(period types.M
ik.SetParams(suite.Ctx, params)
}
func (suite *IntegrationTester) AddIncentiveMultiRewardPeriod(
claimType types.ClaimType,
period types.MultiRewardPeriod,
) {
ik := suite.App.GetIncentiveKeeper()
params := ik.GetParams(suite.Ctx)
for i, rewardPeriod := range params.RewardPeriods {
if claimType == rewardPeriod.ClaimType {
for j, reward := range rewardPeriod.RewardPeriods {
if reward.CollateralType == period.CollateralType {
// Replace existing reward period if the collateralType exists.
// Params are invalid if there are multiple reward periods for the
// same collateral type.
params.RewardPeriods[i].RewardPeriods[j] = period
ik.SetParams(suite.Ctx, params)
return
}
}
// Claim type period exists but not the specific collateral type
params.RewardPeriods[i].RewardPeriods = append(params.RewardPeriods[i].RewardPeriods, period)
return
}
}
// Claim type does not exist in params
params.RewardPeriods = append(params.RewardPeriods, types.NewTypedMultiRewardPeriod(
claimType,
types.MultiRewardPeriods{
period,
},
))
suite.NoError(params.Validate())
ik.SetParams(suite.Ctx, params)
}
// -----------------------------------------------------------------------------
// x/router

View File

@ -0,0 +1,54 @@
package testutil
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/types"
)
// TestKeeper is a test wrapper for the keeper which contains useful methods for testing
type TestKeeper struct {
keeper.Keeper
}
func (keeper TestKeeper) StoreGlobalBorrowIndexes(ctx sdk.Context, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.SetHardBorrowRewardIndexes(ctx, i.CollateralType, i.RewardIndexes)
}
}
func (keeper TestKeeper) StoreGlobalSupplyIndexes(ctx sdk.Context, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.SetHardSupplyRewardIndexes(ctx, i.CollateralType, i.RewardIndexes)
}
}
func (keeper TestKeeper) StoreGlobalDelegatorIndexes(ctx sdk.Context, multiRewardIndexes types.MultiRewardIndexes) {
// Hardcoded to use bond denom
multiRewardIndex, _ := multiRewardIndexes.GetRewardIndex(types.BondDenom)
keeper.SetDelegatorRewardIndexes(ctx, types.BondDenom, multiRewardIndex.RewardIndexes)
}
func (keeper TestKeeper) StoreGlobalSwapIndexes(ctx sdk.Context, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.SetSwapRewardIndexes(ctx, i.CollateralType, i.RewardIndexes)
}
}
func (keeper TestKeeper) StoreGlobalSavingsIndexes(ctx sdk.Context, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.SetSavingsRewardIndexes(ctx, i.CollateralType, i.RewardIndexes)
}
}
func (keeper TestKeeper) StoreGlobalEarnIndexes(ctx sdk.Context, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.SetEarnRewardIndexes(ctx, i.CollateralType, i.RewardIndexes)
}
}
func (keeper TestKeeper) StoreGlobalIndexes(ctx sdk.Context, claimType types.ClaimType, indexes types.MultiRewardIndexes) {
for _, i := range indexes {
keeper.Store.SetRewardIndexes(ctx, claimType, i.CollateralType, i.RewardIndexes)
}
}

View File

@ -216,10 +216,18 @@ type AccrualTimes []AccrualTime
// Validate performs validation of AccrualTimes
func (gats AccrualTimes) Validate() error {
seenAccrualTimes := make(map[string]bool)
for _, gat := range gats {
if err := gat.Validate(); err != nil {
return err
}
key := fmt.Sprintf("%s-%s", gat.ClaimType, gat.CollateralType)
if seenAccrualTimes[key] {
return fmt.Errorf("duplicate accrual time found for %s", key)
}
seenAccrualTimes[key] = true
}
return nil
}

View File

@ -133,6 +133,10 @@ func (p Params) Validate() error {
return err
}
if err := validatedRewardPeriodsParam(p.RewardPeriods); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,11 @@
package types
import sdk "github.com/cosmos/cosmos-sdk/types"
type RewardAccumulator interface {
AccumulateRewards(
ctx sdk.Context,
claimType ClaimType,
rewardPeriod MultiRewardPeriod,
) error
}