mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
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:
parent
44a90a8ef9
commit
cf009647e6
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
19
x/incentive/keeper/accumulators/accumulators_test.go
Normal file
19
x/incentive/keeper/accumulators/accumulators_test.go
Normal 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...) }
|
63
x/incentive/keeper/accumulators/basic.go
Normal file
63
x/incentive/keeper/accumulators/basic.go
Normal 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
|
||||
}
|
404
x/incentive/keeper/accumulators/basic_accum_test.go
Normal file
404
x/incentive/keeper/accumulators/basic_accum_test.go
Normal 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)
|
||||
})
|
||||
}
|
245
x/incentive/keeper/accumulators/earn.go
Normal file
245
x/incentive/keeper/accumulators/earn.go
Normal 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
|
||||
}
|
667
x/incentive/keeper/accumulators/earn_accum_test.go
Normal file
667
x/incentive/keeper/accumulators/earn_accum_test.go
Normal 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)
|
||||
}
|
@ -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,
|
193
x/incentive/keeper/accumulators/earn_staking_test.go
Normal file
193
x/incentive/keeper/accumulators/earn_staking_test.go
Normal 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),
|
||||
},
|
||||
})
|
||||
}
|
50
x/incentive/keeper/accumulators/earn_test.go
Normal file
50
x/incentive/keeper/accumulators/earn_test.go
Normal 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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{
|
||||
{
|
||||
|
95
x/incentive/keeper/store/accrual_time.go
Normal file
95
x/incentive/keeper/store/accrual_time.go
Normal 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
|
||||
}
|
105
x/incentive/keeper/store/claim.go
Normal file
105
x/incentive/keeper/store/claim.go
Normal 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
|
||||
}
|
95
x/incentive/keeper/store/reward_index.go
Normal file
95
x/incentive/keeper/store/reward_index.go
Normal 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
|
||||
}
|
20
x/incentive/keeper/store/store.go
Normal file
20
x/incentive/keeper/store/store.go
Normal 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,
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
54
x/incentive/testutil/keeper.go
Normal file
54
x/incentive/testutil/keeper.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -133,6 +133,10 @@ func (p Params) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validatedRewardPeriodsParam(p.RewardPeriods); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
11
x/incentive/types/reward_accumulator.go
Normal file
11
x/incentive/types/reward_accumulator.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user