mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Incentive refactor: hard rewards (#929)
* organise testing committee gen state * remove repeated test app initialization * minor fixes from linter in tests * move more setup to SetupApp * split up KeeperTestSuite for each reward type * simplify KeeperTestSuite * simplify PayoutKeeperSuite * simplify DelegatorRewardSuite * simplify SupplyRewardsSuite * simplify BorrowRewardsSuite * simplify USDXRewardsSuite * add auth genesis builder for easier test setup * migrate all incentive tests to auth builder * add incentive genesis builder for easier setup migrate hard incentive tests * migrate all tests to incentive builder * add hard genesis builder * small tidy ups * deduplicate initialTime from borrow tests * deduplicate initialtTime from supply tests * deduplicate initialTime from usdx and keeper tests * deduplicate initialTime in delgator tests * deduplicate genesis time in payout test * deduplicate test app initialization * make authGenesisBuilder available for all modules * remove unused pricefeed setup * export incentive genesis builder * remove commented out test cases * migrate cdp test to new test state builders * migrate vv payout tests to use new builders * add SynchronizeHardBorrowReward unit test * extract calculatReward method * tidy up unit test for borrow rewards * add helper method to RewardIndexes * user helper to extract logic from SyncBorrowReward * add Get methods to (Multi)RewardIndexes * replace params.Subspace in keeper to test easier * add unit tests for usdx minting * refactor InitializeUSDXMintingClaim * add unit tests for InitializeHardBorrowRewards * refactor SynchronizeUSDXMintingReward * add unit tests for UpdateHardBorrowIndexDenoms * change rewardSource type to Dec needed by delegation rewards * fix typo in test names * refactor UpdateHardBorrowIndexDenoms * update genesis test TODO to use auth builder * add skipped test for bug in usdx sync * extract common method for calculating rewards * doc comment tidy * add unit tests for delegator rewards * tidy up test files * remove old TODOs * reaarrange InitializeHardDelegatorReward to fit with other init reward functions * duplicate borrow unit tests to create supply tests * add tests for syncing with zero rewards per second * refactor SynchronizeHardDelegatorRewards * refactor supply rewards in same way as borrow * fix total delegation calculation bug * fix new usdx reward bug * fix new supply/borrow reward bug * remove working comment * standardize behaviour when global factors missing * improve documentation for CalculateRewards * standardize variable names * remove panic from calculateSingleReward * wip * Tidy up comments * remove wip comment
This commit is contained in:
parent
15598af3a3
commit
cf16029e77
@ -37,11 +37,10 @@ func (suite *GenesisTestSuite) SetupTest() {
|
||||
suite.genesisTime = time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||
coins := []sdk.Coins{}
|
||||
for j := 0; j < 3; j++ {
|
||||
coins = append(coins, cs(c("bnb", 10_000_000_000), c("ukava", 10_000_000_000)))
|
||||
}
|
||||
authGS := app.NewAuthGenState(addrs, coins)
|
||||
|
||||
authBuilder := app.NewAuthGenesisBuilder().
|
||||
WithSimpleAccount(addrs[0], cs(c("bnb", 1e10), c("ukava", 1e10))).
|
||||
WithSimpleModuleAccount(kavadist.KavaDistMacc, cs(c("hard", 1e15), c("ukava", 1e15)))
|
||||
|
||||
loanToValue, _ := sdk.NewDecFromStr("0.6")
|
||||
borrowLimit := sdk.NewDec(1000000000000000)
|
||||
@ -78,7 +77,7 @@ func (suite *GenesisTestSuite) SetupTest() {
|
||||
)
|
||||
tApp.InitializeFromGenesisStatesWithTime(
|
||||
suite.genesisTime,
|
||||
authGS,
|
||||
authBuilder.BuildMarshalled(),
|
||||
app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)},
|
||||
app.GenesisState{hard.ModuleName: hard.ModuleCdc.MustMarshalJSON(hardGS)},
|
||||
NewCDPGenStateMulti(),
|
||||
@ -87,14 +86,6 @@ func (suite *GenesisTestSuite) SetupTest() {
|
||||
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: suite.genesisTime})
|
||||
|
||||
// TODO add to auth gen state to tidy up test
|
||||
err := tApp.GetSupplyKeeper().MintCoins(
|
||||
ctx,
|
||||
kavadist.KavaDistMacc,
|
||||
cs(c("hard", 1_000_000_000_000_000), c("ukava", 1_000_000_000_000_000)),
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.addrs = addrs
|
||||
suite.keeper = keeper
|
||||
suite.app = tApp
|
||||
|
@ -81,6 +81,8 @@ When delegated tokens (to bonded validators) are changed:
|
||||
- jail: total bonded delegation decreases (tokens no longer bonded (after end blocker runs))
|
||||
- validator becomes unbonded (ie when they drop out of the top 100)
|
||||
- total bonded delegation decreases (tokens no longer bonded)
|
||||
- validator becomes bonded (ie when they're promoted into the top 100)
|
||||
- total bonded delegation increases (tokens become bonded)
|
||||
|
||||
*/
|
||||
|
||||
|
@ -262,9 +262,6 @@ func (builder IncentiveGenesisBuilder) WithSimpleBorrowRewardPeriod(ctype string
|
||||
))
|
||||
}
|
||||
func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
||||
// TODO this could set the start/end times on the period according to builder.genesisTime
|
||||
// Then they could be created by a different builder
|
||||
|
||||
builder.Params.HardSupplyRewardPeriods = append(builder.Params.HardSupplyRewardPeriods, period)
|
||||
|
||||
accumulationTimeForPeriod := types.NewGenesisAccumulationTime(period.CollateralType, builder.genesisTime)
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
@ -18,24 +17,28 @@ type Keeper struct {
|
||||
cdpKeeper types.CdpKeeper
|
||||
hardKeeper types.HardKeeper
|
||||
key sdk.StoreKey
|
||||
paramSubspace subspace.Subspace
|
||||
paramSubspace types.ParamSubspace
|
||||
supplyKeeper types.SupplyKeeper
|
||||
stakingKeeper types.StakingKeeper
|
||||
}
|
||||
|
||||
// NewKeeper creates a new keeper
|
||||
func NewKeeper(
|
||||
cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper,
|
||||
cdc *codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, sk types.SupplyKeeper,
|
||||
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper,
|
||||
) Keeper {
|
||||
|
||||
if !paramstore.HasKeyTable() {
|
||||
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
|
||||
}
|
||||
|
||||
return Keeper{
|
||||
accountKeeper: ak,
|
||||
cdc: cdc,
|
||||
cdpKeeper: cdpk,
|
||||
hardKeeper: hk,
|
||||
key: key,
|
||||
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
|
||||
paramSubspace: paramstore,
|
||||
supplyKeeper: sk,
|
||||
stakingKeeper: stk,
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
@ -85,14 +86,11 @@ func (k Keeper) InitializeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bor
|
||||
|
||||
var borrowRewardIndexes types.MultiRewardIndexes
|
||||
for _, coin := range borrow.Amount {
|
||||
globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom)
|
||||
var multiRewardIndex types.MultiRewardIndex
|
||||
if foundGlobalRewardIndexes {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes)
|
||||
} else {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{})
|
||||
globalRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, coin.Denom)
|
||||
if !found {
|
||||
globalRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex)
|
||||
borrowRewardIndexes = borrowRewardIndexes.With(coin.Denom, globalRewardIndexes)
|
||||
}
|
||||
|
||||
claim.BorrowRewardIndexes = borrowRewardIndexes
|
||||
@ -108,49 +106,34 @@ func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Bo
|
||||
}
|
||||
|
||||
for _, coin := range borrow.Amount {
|
||||
globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, coin.Denom)
|
||||
if !foundGlobalRewardIndexes {
|
||||
globalRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, coin.Denom)
|
||||
if !found {
|
||||
// The global factor is only not found if
|
||||
// - the borrowed denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
|
||||
// - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist)
|
||||
// If not found we could either skip this sync, or assume the global factor is zero.
|
||||
// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
|
||||
// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
|
||||
continue
|
||||
}
|
||||
|
||||
userMultiRewardIndex, foundUserMultiRewardIndex := claim.BorrowRewardIndexes.GetRewardIndex(coin.Denom)
|
||||
if !foundUserMultiRewardIndex {
|
||||
continue
|
||||
userRewardIndexes, found := claim.BorrowRewardIndexes.Get(coin.Denom)
|
||||
if !found {
|
||||
// Normally the reward indexes should always be found.
|
||||
// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that borrowed denom.
|
||||
// So given the reward period was just added, assume the starting value for any global reward indexes, which is an empty slice.
|
||||
userRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
|
||||
userRewardIndexIndex, foundUserRewardIndexIndex := claim.BorrowRewardIndexes.GetRewardIndexIndex(coin.Denom)
|
||||
if !foundUserRewardIndexIndex {
|
||||
continue
|
||||
newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, coin.Amount.ToDec())
|
||||
if err != nil {
|
||||
// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
|
||||
// This panics if a global reward factor decreases or disappears between the old and new indexes.
|
||||
panic(fmt.Sprintf("corrupted global reward indexes found: %v", err))
|
||||
}
|
||||
|
||||
for _, globalRewardIndex := range globalRewardIndexes {
|
||||
userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType)
|
||||
if !foundUserRewardIndex {
|
||||
// User borrowed this coin type before it had rewards. When new rewards are added, legacy borrowers
|
||||
// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
|
||||
// reward index denom and start their reward factor at 0.0
|
||||
userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec())
|
||||
userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex)
|
||||
claim.BorrowRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex
|
||||
}
|
||||
|
||||
globalRewardFactor := globalRewardIndex.RewardFactor
|
||||
userRewardFactor := userRewardIndex.RewardFactor
|
||||
rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsNegative() {
|
||||
panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor))
|
||||
}
|
||||
|
||||
newRewardsAmount := rewardsAccumulatedFactor.Mul(borrow.Amount.AmountOf(coin.Denom).ToDec()).RoundInt()
|
||||
|
||||
factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType)
|
||||
if !foundFactorIndex { // should never trigger
|
||||
continue
|
||||
}
|
||||
claim.BorrowRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor
|
||||
newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount)
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
}
|
||||
claim.Reward = claim.Reward.Add(newRewards...)
|
||||
claim.BorrowRewardIndexes = claim.BorrowRewardIndexes.With(coin.Denom, globalRewardIndexes)
|
||||
}
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
@ -165,26 +148,22 @@ func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Bo
|
||||
borrowDenoms := getDenoms(borrow.Amount)
|
||||
borrowRewardIndexDenoms := claim.BorrowRewardIndexes.GetCollateralTypes()
|
||||
|
||||
uniqueBorrowDenoms := setDifference(borrowDenoms, borrowRewardIndexDenoms)
|
||||
uniqueBorrowRewardDenoms := setDifference(borrowRewardIndexDenoms, borrowDenoms)
|
||||
|
||||
borrowRewardIndexes := claim.BorrowRewardIndexes
|
||||
|
||||
// Create a new multi-reward index in the claim for every new borrow denom
|
||||
uniqueBorrowDenoms := setDifference(borrowDenoms, borrowRewardIndexDenoms)
|
||||
|
||||
for _, denom := range uniqueBorrowDenoms {
|
||||
_, foundUserRewardIndexes := claim.BorrowRewardIndexes.GetRewardIndex(denom)
|
||||
if !foundUserRewardIndexes {
|
||||
globalBorrowRewardIndexes, foundGlobalBorrowRewardIndexes := k.GetHardBorrowRewardIndexes(ctx, denom)
|
||||
var multiRewardIndex types.MultiRewardIndex
|
||||
if foundGlobalBorrowRewardIndexes {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(denom, globalBorrowRewardIndexes)
|
||||
} else {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{})
|
||||
}
|
||||
borrowRewardIndexes = append(borrowRewardIndexes, multiRewardIndex)
|
||||
globalBorrowRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, denom)
|
||||
if !found {
|
||||
globalBorrowRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
borrowRewardIndexes = borrowRewardIndexes.With(denom, globalBorrowRewardIndexes)
|
||||
}
|
||||
|
||||
// Delete multi-reward index from claim if the collateral type is no longer borrowed
|
||||
uniqueBorrowRewardDenoms := setDifference(borrowRewardIndexDenoms, borrowDenoms)
|
||||
|
||||
for _, denom := range uniqueBorrowRewardDenoms {
|
||||
borrowRewardIndexes = borrowRewardIndexes.RemoveRewardIndex(denom)
|
||||
}
|
||||
@ -192,3 +171,52 @@ func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Bo
|
||||
claim.BorrowRewardIndexes = borrowRewardIndexes
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// CalculateRewards computes how much rewards should have accrued to a source (eg a user's hard borrowed btc amount)
|
||||
// between two index values.
|
||||
//
|
||||
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a hard borrowed/supplied amount.
|
||||
//
|
||||
// Returns an error if newIndexes does not contain all CollateralTypes from oldIndexes, or if any value of oldIndex.RewardFactor > newIndex.RewardFactor.
|
||||
// This should never happen, as it would mean that a global reward index has decreased in value, or that a global reward index has been deleted from state.
|
||||
func (k Keeper) CalculateRewards(oldIndexes, newIndexes types.RewardIndexes, rewardSource sdk.Dec) (sdk.Coins, error) {
|
||||
// check for missing CollateralType's
|
||||
for _, oldIndex := range oldIndexes {
|
||||
if newIndex, found := newIndexes.Get(oldIndex.CollateralType); !found {
|
||||
return nil, sdkerrors.Wrapf(types.ErrDecreasingRewardFactor, "old: %v, new: %v", oldIndex, newIndex)
|
||||
}
|
||||
}
|
||||
var reward sdk.Coins
|
||||
for _, newIndex := range newIndexes {
|
||||
oldFactor, found := oldIndexes.Get(newIndex.CollateralType)
|
||||
if !found {
|
||||
oldFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
rewardAmount, err := k.CalculateSingleReward(oldFactor, newIndex.RewardFactor, rewardSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reward = reward.Add(
|
||||
sdk.NewCoin(newIndex.CollateralType, rewardAmount),
|
||||
)
|
||||
}
|
||||
return reward, nil
|
||||
}
|
||||
|
||||
// CalculateSingleReward computes how much rewards should have accrued to a source (eg a user's btcb-a cdp principal)
|
||||
// between two index values.
|
||||
//
|
||||
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a cdp principal amount.
|
||||
//
|
||||
// Returns an error if oldIndex > newIndex. This should never happen, as it would mean that a global reward index has decreased in value,
|
||||
// or that a global reward index has been deleted from state.
|
||||
func (k Keeper) CalculateSingleReward(oldIndex, newIndex, rewardSource sdk.Dec) (sdk.Int, error) {
|
||||
increase := newIndex.Sub(oldIndex)
|
||||
if increase.IsNegative() {
|
||||
return sdk.Int{}, sdkerrors.Wrapf(types.ErrDecreasingRewardFactor, "old: %v, new: %v", oldIndex, newIndex)
|
||||
}
|
||||
reward := increase.Mul(rewardSource).RoundInt()
|
||||
return reward, nil
|
||||
}
|
||||
|
89
x/incentive/keeper/rewards_borrow_init_test.go
Normal file
89
x/incentive/keeper/rewards_borrow_init_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// InitializeHardBorrowRewardTests runs unit tests for the keeper.InitializeHardBorrowReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.BorrowRewardIndexes)
|
||||
// - global indexes in store
|
||||
// - borrow function arg (only borrow.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets or creates a claim
|
||||
type InitializeHardBorrowRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestInitializeHardBorrowReward(t *testing.T) {
|
||||
suite.Run(t, new(InitializeHardBorrowRewardTests))
|
||||
}
|
||||
|
||||
func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetWhenClaimExists() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
// Indexes should always be empty when initialize is called.
|
||||
// If initialize is called then the user must have repaid their borrow positions,
|
||||
// which means UpdateHardBorrowIndexDenoms was called and should have remove indexes.
|
||||
BorrowRewardIndexes: types.MultiRewardIndexes{},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetWhenClaimDoesNotExist() {
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, owner)
|
||||
suite.True(found)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
func (suite *InitializeHardBorrowRewardTests) TestClaimIndexesAreSetEmptyForMissingIndexes() {
|
||||
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
// Borrow a denom that is not in the global indexes.
|
||||
// This happens when a borrow denom has no rewards associated with it.
|
||||
expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
|
||||
borrowedDenoms := extractCollateralTypes(expectedIndexes)
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: owner,
|
||||
Amount: arbitraryCoinsWithDenoms(borrowedDenoms...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
528
x/incentive/keeper/rewards_borrow_sync_test.go
Normal file
528
x/incentive/keeper/rewards_borrow_sync_test.go
Normal file
@ -0,0 +1,528 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// SynchronizeHardBorrowRewardTests runs unit tests for the keeper.SynchronizeHardBorrowReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store (only claim.BorrowRewardIndexes, claim.Reward)
|
||||
// - global indexes in store
|
||||
// - borrow function arg (only borrow.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets a claim
|
||||
type SynchronizeHardBorrowRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestSynchronizeHardBorrowReward(t *testing.T) {
|
||||
suite.Run(t, new(SynchronizeHardBorrowRewardTests))
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenGlobalIndexesHaveIncreased() {
|
||||
// This is the normal case
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(claim.BorrowRewardIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUnchangedWhenGlobalIndexesUnchanged() {
|
||||
// It should be safe to call SynchronizeHardBorrowReward multiple times
|
||||
|
||||
unchangingIndexes := nonEmptyMultiRewardIndexes
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: unchangingIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalBorrowIndexes(unchangingIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(unchangingIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(unchangingIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenNewRewardAdded() {
|
||||
// When a new reward is added (via gov) for a hard borrow denom the user has already borrowed, and the claim is synced;
|
||||
// Then the new reward's index should be added to the claim.
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestClaimIndexesAreUpdatedWhenNewRewardDenomAdded() {
|
||||
// When a new reward coin is added (via gov) to an already rewarded borrow denom (that the user has already borrowed), and the claim is synced;
|
||||
// Then the new reward coin's index should be added to the claim.
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenGlobalIndexesHaveIncreased() {
|
||||
// This is the normal case
|
||||
// Given some time has passed (meaning the global indexes have increased)
|
||||
// When the claim is synced
|
||||
// The user earns rewards for the time passed
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
BorrowRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "borrowdenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalBorrowIndexes(types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "borrowdenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: cs(c("borrowdenom", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
// new reward is (new index - old index) * borrow amount
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("rewarddenom", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenNewRewardAdded() {
|
||||
// When a new reward is added (via gov) for a hard borrow denom the user has already borrowed, and the claim is synced
|
||||
// Then the user earns rewards for the time since the reward was added
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
BorrowRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CollateralType: "newlyrewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "otherreward",
|
||||
// Indexes start at 0 when the reward is added by gov,
|
||||
// so this represents the syncing happening some time later.
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: cs(c("rewarded", 1e9), c("newlyrewarded", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
// new reward is (new index - old index) * borrow amount for each borrowed denom
|
||||
// The old index for `newlyrewarded` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("otherreward", 1_000_001_000_000), c("reward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
func (suite *SynchronizeHardBorrowRewardTests) TestRewardIsIncrementedWhenNewRewardDenomAdded() {
|
||||
// When a new reward coin is added (via gov) to an already rewarded borrow denom (that the user has already borrowed), and the claim is synced;
|
||||
// Then the user earns rewards for the time since the reward was added
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
BorrowRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "borrowed",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "borrowed",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
{
|
||||
CollateralType: "otherreward",
|
||||
// Indexes start at 0 when the reward is added by gov,
|
||||
// so this represents the syncing happening some time later.
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: cs(c("borrowed", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
|
||||
|
||||
// new reward is (new index - old index) * borrow amount for each borrowed denom
|
||||
// The old index for `otherreward` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("reward", 1_000_001_000_000), c("otherreward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func TestCalculateRewards(t *testing.T) {
|
||||
type expected struct {
|
||||
err error
|
||||
coins sdk.Coins
|
||||
}
|
||||
type args struct {
|
||||
oldIndexes, newIndexes types.RewardIndexes
|
||||
sourceAmount sdk.Dec
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "when old and new indexes have same denoms, rewards are calculated correctly",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.000000001"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
},
|
||||
newIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("1000.0"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.100000001"),
|
||||
},
|
||||
},
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
// for each denom: (new - old) * sourceAmount
|
||||
coins: cs(c("hard", 999999999999), c("ukava", 1)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when new indexes have an extra denom, rewards are calculated as if it was 0 in old indexes",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.000000001"),
|
||||
},
|
||||
},
|
||||
newIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("1000.0"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.100000001"),
|
||||
},
|
||||
},
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
// for each denom: (new - old) * sourceAmount
|
||||
coins: cs(c("hard", 999999999999), c("ukava", 100000001)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when new indexes are smaller than old, an error is returned",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.2"),
|
||||
},
|
||||
},
|
||||
newIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
},
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
err: types.ErrDecreasingRewardFactor,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when old indexes have an extra denom, an error is returned",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
},
|
||||
newIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.2"),
|
||||
},
|
||||
},
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
err: types.ErrDecreasingRewardFactor,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when old and new indexes are 0, rewards are 0",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.0"),
|
||||
},
|
||||
},
|
||||
newIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.0"),
|
||||
},
|
||||
},
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
coins: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when old and new indexes are empty, rewards are 0",
|
||||
args: args{
|
||||
oldIndexes: types.RewardIndexes{},
|
||||
newIndexes: nil,
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
coins: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
coins, err := keeper.Keeper{}.CalculateRewards(tc.args.oldIndexes, tc.args.newIndexes, tc.args.sourceAmount)
|
||||
if tc.expected.err != nil {
|
||||
require.True(t, errors.Is(err, tc.expected.err))
|
||||
} else {
|
||||
require.Equal(t, tc.expected.coins, coins)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestCalculateSingleReward(t *testing.T) {
|
||||
type expected struct {
|
||||
err error
|
||||
reward sdk.Int
|
||||
}
|
||||
type args struct {
|
||||
oldIndex, newIndex sdk.Dec
|
||||
sourceAmount sdk.Dec
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "when new index is > old, rewards are calculated correctly",
|
||||
args: args{
|
||||
oldIndex: d("0.000000001"),
|
||||
newIndex: d("1000.0"),
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
// (new - old) * sourceAmount
|
||||
reward: i(999999999999),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when new index is < old, an error is returned",
|
||||
args: args{
|
||||
oldIndex: d("0.000000001"),
|
||||
newIndex: d("0.0"),
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
err: types.ErrDecreasingRewardFactor,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when old and new indexes are 0, rewards are 0",
|
||||
args: args{
|
||||
oldIndex: d("0.0"),
|
||||
newIndex: d("0.0"),
|
||||
sourceAmount: d("1000000000"),
|
||||
},
|
||||
expected: expected{
|
||||
reward: sdk.ZeroInt(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
reward, err := keeper.Keeper{}.CalculateSingleReward(tc.args.oldIndex, tc.args.newIndex, tc.args.sourceAmount)
|
||||
if tc.expected.err != nil {
|
||||
require.True(t, errors.Is(err, tc.expected.err))
|
||||
} else {
|
||||
require.Equal(t, tc.expected.reward, reward)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ func (suite *BorrowRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenes
|
||||
authBuilder.BuildMarshalled(),
|
||||
NewPricefeedGenStateMultiFromTime(suite.genesisTime),
|
||||
hardBuilder.BuildMarshalled(),
|
||||
NewCommitteeGenesisState(suite.addrs[:2]), // TODO add committee members to suite,
|
||||
NewCommitteeGenesisState(suite.addrs[:2]),
|
||||
incentBuilder.BuildMarshalled(),
|
||||
)
|
||||
}
|
||||
@ -498,7 +498,6 @@ func (suite *BorrowRewardsTestSuite) TestSynchronizeHardBorrowReward() {
|
||||
updatedTimeDuration: 86400,
|
||||
},
|
||||
},
|
||||
// TODO test synchronize when there is a reward period with 0 rewardsPerSecond
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
|
119
x/incentive/keeper/rewards_borrow_update_test.go
Normal file
119
x/incentive/keeper/rewards_borrow_update_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// UpdateHardBorrowIndexDenomsTests runs unit tests for the keeper.UpdateHardBorrowIndexDenoms method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.BorrowRewardIndexes)
|
||||
// - global indexes in store
|
||||
// - borrow function arg (only borrow.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets a claim
|
||||
type UpdateHardBorrowIndexDenomsTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestUpdateHardBorrowIndexDenoms(t *testing.T) {
|
||||
suite.Run(t, new(UpdateHardBorrowIndexDenomsTests))
|
||||
}
|
||||
|
||||
func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreRemovedForDenomsNoLongerBorrowed() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
suite.storeGlobalBorrowIndexes(claim.BorrowRewardIndexes)
|
||||
|
||||
// remove one denom from the indexes already in the borrow
|
||||
expectedIndexes := claim.BorrowRewardIndexes[1:]
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(expectedIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreAddedForNewlyBorrowedDenoms() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
globalIndexes := appendUniqueMultiRewardIndex(claim.BorrowRewardIndexes)
|
||||
suite.storeGlobalBorrowIndexes(globalIndexes)
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardBorrowIndexDenomsTests) TestClaimIndexesAreUnchangedWhenBorrowedDenomsUnchanged() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
// Set global indexes with same denoms but different values.
|
||||
// UpdateHardBorrowIndexDenoms should ignore the new values.
|
||||
suite.storeGlobalBorrowIndexes(increaseAllRewardFactors(claim.BorrowRewardIndexes))
|
||||
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(claim.BorrowRewardIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(claim.BorrowRewardIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardBorrowIndexDenomsTests) TestEmptyClaimIndexesAreAddedForNewlyBorrowedButNotRewardedDenoms() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
BorrowRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
suite.storeGlobalBorrowIndexes(claim.BorrowRewardIndexes)
|
||||
|
||||
// add a denom to the borrowed amount that is not in the global or claim's indexes
|
||||
expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.BorrowRewardIndexes)
|
||||
borrowedDenoms := extractCollateralTypes(expectedIndexes)
|
||||
borrow := hardtypes.Borrow{
|
||||
Borrower: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(borrowedDenoms...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardBorrowIndexDenoms(suite.ctx, borrow)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.BorrowRewardIndexes)
|
||||
}
|
@ -45,23 +45,21 @@ func (k Keeper) AccumulateHardDelegatorRewards(ctx sdk.Context, rewardPeriod typ
|
||||
|
||||
// InitializeHardDelegatorReward initializes the delegator reward index of a hard claim
|
||||
func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.AccAddress) {
|
||||
delegatorFactor, foundDelegatorFactor := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
if !foundDelegatorFactor { // Should always be found...
|
||||
delegatorFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
delegatorRewardIndexes := types.NewRewardIndex(types.BondDenom, delegatorFactor)
|
||||
|
||||
claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||
if !found {
|
||||
// Instantiate claim object
|
||||
claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil)
|
||||
} else {
|
||||
k.SynchronizeHardDelegatorRewards(ctx, delegator, nil, false)
|
||||
claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||
}
|
||||
|
||||
claim.DelegatorRewardIndexes = types.RewardIndexes{delegatorRewardIndexes}
|
||||
globalRewardFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
if !found {
|
||||
// If there's no global delegator reward factor, initialize the claim with a delegator factor of zero.
|
||||
globalRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
claim.DelegatorRewardIndexes = types.RewardIndexes{types.NewRewardIndex(types.BondDenom, globalRewardFactor)}
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
@ -74,23 +72,42 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
|
||||
return
|
||||
}
|
||||
|
||||
delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
globalRewardFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
if !found {
|
||||
// The global factor is only not found if
|
||||
// - the bond denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
|
||||
// - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist)
|
||||
// If not found we could either skip this sync, or assume the global factor is zero.
|
||||
// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
|
||||
// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
|
||||
return
|
||||
}
|
||||
|
||||
delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom)
|
||||
if !hasDelegatorRewardIndex {
|
||||
return
|
||||
userRewardFactor, found := claim.DelegatorRewardIndexes.Get(types.BondDenom)
|
||||
if !found {
|
||||
// Normally the factor should always be found, as it is added in InitializeHardDelegatorReward when a user delegates.
|
||||
// However if there were no delegator rewards (ie no reward period in params) then a reward period is added, existing claims will not have the factor.
|
||||
// So assume the factor is the starting value for any global factor: 0.
|
||||
userRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor
|
||||
rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsNegative() {
|
||||
panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor))
|
||||
}
|
||||
claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor
|
||||
totalDelegated := k.GetTotalDelegated(ctx, delegator, valAddr, shouldIncludeValidator)
|
||||
|
||||
rewardsEarned, err := k.CalculateSingleReward(userRewardFactor, globalRewardFactor, totalDelegated)
|
||||
if err != nil {
|
||||
// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
|
||||
// This panics if a global reward factor decreases or disappears between the old and new indexes.
|
||||
panic(fmt.Sprintf("corrupted global reward indexes found: %v", err))
|
||||
}
|
||||
newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned)
|
||||
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
claim.DelegatorRewardIndexes = claim.DelegatorRewardIndexes.With(types.BondDenom, globalRewardFactor)
|
||||
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
func (k Keeper) GetTotalDelegated(ctx sdk.Context, delegator sdk.AccAddress, valAddr sdk.ValAddress, shouldIncludeValidator bool) sdk.Dec {
|
||||
totalDelegated := sdk.ZeroDec()
|
||||
|
||||
delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, 200)
|
||||
@ -100,14 +117,16 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
|
||||
continue
|
||||
}
|
||||
|
||||
if valAddr == nil {
|
||||
// Delegators don't accumulate rewards if their validator is unbonded
|
||||
if validator.GetStatus() != sdk.Bonded {
|
||||
if validator.OperatorAddress.Equals(valAddr) {
|
||||
if shouldIncludeValidator {
|
||||
// do nothing, so the validator is included regardless of bonded status
|
||||
} else {
|
||||
// skip this validator
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !shouldIncludeValidator && validator.OperatorAddress.Equals(valAddr) {
|
||||
// ignore tokens delegated to the validator
|
||||
// skip any not bonded validator
|
||||
if validator.GetStatus() != sdk.Bonded {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -122,10 +141,5 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
|
||||
}
|
||||
totalDelegated = totalDelegated.Add(delegatedTokens)
|
||||
}
|
||||
rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt()
|
||||
|
||||
// Add rewards to delegator's hard claim
|
||||
newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned)
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
return totalDelegated
|
||||
}
|
||||
|
115
x/incentive/keeper/rewards_delegator_init_test.go
Normal file
115
x/incentive/keeper/rewards_delegator_init_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// InitializeHardDelegatorRewardTests runs unit tests for the keeper.InitializeHardDelegatorReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.DelegatorRewardIndexes)
|
||||
// - global indexes in store
|
||||
// - delegator function arg
|
||||
//
|
||||
// outputs
|
||||
// - sets or creates a claim
|
||||
type InitializeHardDelegatorRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestInitializeHardDelegatorReward(t *testing.T) {
|
||||
suite.Run(t, new(InitializeHardDelegatorRewardTests))
|
||||
}
|
||||
|
||||
func (suite *InitializeHardDelegatorRewardTests) storeGlobalDelegatorFactor(rewardIndexes types.RewardIndexes) {
|
||||
factor := rewardIndexes[0]
|
||||
suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, factor.CollateralType, factor.RewardFactor)
|
||||
}
|
||||
|
||||
func (suite *InitializeHardDelegatorRewardTests) TestClaimIndexesAreSetWhenClaimDoesNotExist() {
|
||||
globalIndex := arbitraryDelegatorRewardIndexes
|
||||
suite.storeGlobalDelegatorFactor(globalIndex)
|
||||
|
||||
delegator := arbitraryAddress()
|
||||
suite.keeper.InitializeHardDelegatorReward(suite.ctx, delegator)
|
||||
|
||||
syncedClaim, f := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, delegator)
|
||||
suite.True(f)
|
||||
suite.Equal(globalIndex, syncedClaim.DelegatorRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *InitializeHardDelegatorRewardTests) TestClaimIsSyncedAndIndexesAreSetWhenClaimDoesExist() {
|
||||
validatorAddress := arbitraryValidatorAddress()
|
||||
sk := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{{
|
||||
ValidatorAddress: validatorAddress,
|
||||
Shares: d("1000"),
|
||||
}},
|
||||
validators: stakingtypes.Validators{{
|
||||
OperatorAddress: validatorAddress,
|
||||
Status: sdk.Bonded,
|
||||
Tokens: i(1000),
|
||||
DelegatorShares: d("1000"),
|
||||
}},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, sk)
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
DelegatorRewardIndexes: arbitraryDelegatorRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
// Set the global factor to a value different to one in claim so
|
||||
// we can detect if it is overwritten.
|
||||
globalIndex := increaseRewardFactors(claim.DelegatorRewardIndexes)
|
||||
suite.storeGlobalDelegatorFactor(globalIndex)
|
||||
|
||||
suite.keeper.InitializeHardDelegatorReward(suite.ctx, claim.Owner)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndex, syncedClaim.DelegatorRewardIndexes)
|
||||
suite.Truef(syncedClaim.Reward.IsAllGT(claim.Reward), "'%s' not greater than '%s'", syncedClaim.Reward, claim.Reward)
|
||||
}
|
||||
|
||||
// arbitraryDelegatorRewardIndexes contains only one reward index as there is only every one bond denom
|
||||
var arbitraryDelegatorRewardIndexes = types.RewardIndexes{
|
||||
types.NewRewardIndex(types.BondDenom, d("0.2")),
|
||||
}
|
||||
|
||||
type fakeStakingKeeper struct {
|
||||
delegations stakingtypes.Delegations
|
||||
validators stakingtypes.Validators
|
||||
}
|
||||
|
||||
func (k fakeStakingKeeper) TotalBondedTokens(ctx sdk.Context) sdk.Int {
|
||||
panic("unimplemented")
|
||||
}
|
||||
func (k fakeStakingKeeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) []stakingtypes.Delegation {
|
||||
return k.delegations
|
||||
}
|
||||
func (k fakeStakingKeeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (stakingtypes.Validator, bool) {
|
||||
for _, val := range k.validators {
|
||||
if val.GetOperator().Equals(addr) {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return stakingtypes.Validator{}, false
|
||||
}
|
||||
func (k fakeStakingKeeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) []stakingtypes.Delegation {
|
||||
var delegations stakingtypes.Delegations
|
||||
for _, d := range k.delegations {
|
||||
if d.ValidatorAddress.Equals(valAddr) {
|
||||
delegations = append(delegations, d)
|
||||
}
|
||||
}
|
||||
return delegations
|
||||
}
|
356
x/incentive/keeper/rewards_delegator_sync_test.go
Normal file
356
x/incentive/keeper/rewards_delegator_sync_test.go
Normal file
@ -0,0 +1,356 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// SynchronizeHardDelegatorRewardTests runs unit tests for the keeper.SynchronizeHardDelegatorReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.DelegatorRewardIndexes and claim.Reward)
|
||||
// - global index in store
|
||||
// - function args: delegator address, validator address, shouldIncludeValidator flag
|
||||
// - delegator's delegations and the corresponding validators
|
||||
//
|
||||
// outputs
|
||||
// - sets or creates a claim
|
||||
type SynchronizeHardDelegatorRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestSynchronizeHardDelegatorReward(t *testing.T) {
|
||||
suite.Run(t, new(SynchronizeHardDelegatorRewardTests))
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) storeGlobalDelegatorFactor(rewardIndexes types.RewardIndexes) {
|
||||
factor := rewardIndexes[0]
|
||||
suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, factor.CollateralType, factor.RewardFactor)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGlobalFactorUnchanged() {
|
||||
delegator := arbitraryAddress()
|
||||
|
||||
stakingKeeper := fakeStakingKeeper{} // use an empty staking keeper that returns no delegations
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: delegator,
|
||||
},
|
||||
DelegatorRewardIndexes: arbitraryDelegatorRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalDelegatorFactor(claim.DelegatorRewardIndexes)
|
||||
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, claim.Owner, nil, false)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(claim.DelegatorRewardIndexes, syncedClaim.DelegatorRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlobalFactorIncreased() {
|
||||
delegator := arbitraryAddress()
|
||||
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, fakeStakingKeeper{})
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: delegator,
|
||||
},
|
||||
DelegatorRewardIndexes: arbitraryDelegatorRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := increaseRewardFactors(claim.DelegatorRewardIndexes)
|
||||
suite.storeGlobalDelegatorFactor(globalIndexes)
|
||||
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, claim.Owner, nil, false)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.DelegatorRewardIndexes)
|
||||
}
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFactorUnchanged() {
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddress := arbitraryValidatorAddress()
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddress,
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddress),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: delegator,
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
DelegatorRewardIndexes: types.RewardIndexes{{
|
||||
CollateralType: types.BondDenom,
|
||||
RewardFactor: d("0.1"),
|
||||
}},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalDelegatorFactor(claim.DelegatorRewardIndexes)
|
||||
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, claim.Owner, nil, false)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
|
||||
suite.Equal(claim.Reward, syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestRewardIsIncreasedWhenNewRewardAdded() {
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddress := arbitraryValidatorAddress()
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddress,
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddress),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: delegator,
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
DelegatorRewardIndexes: types.RewardIndexes{},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
newGlobalIndexes := types.RewardIndexes{{
|
||||
CollateralType: types.BondDenom,
|
||||
RewardFactor: d("0.1"),
|
||||
}}
|
||||
suite.storeGlobalDelegatorFactor(newGlobalIndexes)
|
||||
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, claim.Owner, nil, false)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
|
||||
suite.Equal(newGlobalIndexes, syncedClaim.DelegatorRewardIndexes)
|
||||
suite.Equal(
|
||||
cs(c(types.HardLiquidityRewardDenom, 100)).Add(claim.Reward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFactorIncreased() {
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddress := arbitraryValidatorAddress()
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddress,
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddress),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: delegator,
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
DelegatorRewardIndexes: types.RewardIndexes{{
|
||||
CollateralType: types.BondDenom,
|
||||
RewardFactor: d("0.1"),
|
||||
}},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalDelegatorFactor(
|
||||
types.RewardIndexes{
|
||||
types.NewRewardIndex(types.BondDenom, d("0.2")),
|
||||
},
|
||||
)
|
||||
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, claim.Owner, nil, false)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
|
||||
suite.Equal(
|
||||
cs(c(types.HardLiquidityRewardDenom, 100)).Add(claim.Reward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func unslashedBondedValidator(address sdk.ValAddress) stakingtypes.Validator {
|
||||
return stakingtypes.Validator{
|
||||
OperatorAddress: address,
|
||||
Status: sdk.Bonded,
|
||||
|
||||
// Set the tokens and shares equal so then
|
||||
// a _delegator's_ token amount is equal to their shares amount
|
||||
Tokens: i(1e12),
|
||||
DelegatorShares: i(1e12).ToDec(),
|
||||
}
|
||||
}
|
||||
func unslashedNotBondedValidator(address sdk.ValAddress) stakingtypes.Validator {
|
||||
return stakingtypes.Validator{
|
||||
OperatorAddress: address,
|
||||
Status: sdk.Unbonding,
|
||||
|
||||
// Set the tokens and shares equal so then
|
||||
// a _delegator's_ token amount is equal to their shares amount
|
||||
Tokens: i(1e12),
|
||||
DelegatorShares: i(1e12).ToDec(),
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestGetDelegatedWhenValAddrIsNil() {
|
||||
// when valAddr is nil, get total delegated to bonded validators
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddresses := generateValidatorAddresses(4)
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
//bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[0],
|
||||
Shares: d("1"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[1],
|
||||
Shares: d("10"),
|
||||
},
|
||||
// not bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[2],
|
||||
Shares: d("100"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[3],
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddresses[0]),
|
||||
unslashedBondedValidator(validatorAddresses[1]),
|
||||
unslashedNotBondedValidator(validatorAddresses[2]),
|
||||
unslashedNotBondedValidator(validatorAddresses[3]),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
suite.Equal(
|
||||
d("11"), // delegation to bonded validators
|
||||
suite.keeper.GetTotalDelegated(suite.ctx, delegator, nil, false),
|
||||
)
|
||||
}
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestGetDelegatedWhenExcludingAValidator() {
|
||||
// when valAddr is x, get total delegated to bonded validators excluding those to x
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddresses := generateValidatorAddresses(4)
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
//bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[0],
|
||||
Shares: d("1"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[1],
|
||||
Shares: d("10"),
|
||||
},
|
||||
// not bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[2],
|
||||
Shares: d("100"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[3],
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddresses[0]),
|
||||
unslashedBondedValidator(validatorAddresses[1]),
|
||||
unslashedNotBondedValidator(validatorAddresses[2]),
|
||||
unslashedNotBondedValidator(validatorAddresses[3]),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
suite.Equal(
|
||||
d("10"),
|
||||
suite.keeper.GetTotalDelegated(suite.ctx, delegator, validatorAddresses[0], false),
|
||||
)
|
||||
}
|
||||
func (suite *SynchronizeHardDelegatorRewardTests) TestGetDelegatedWhenIncludingAValidator() {
|
||||
// when valAddr is x, get total delegated to bonded validators including those to x
|
||||
delegator := arbitraryAddress()
|
||||
validatorAddresses := generateValidatorAddresses(4)
|
||||
stakingKeeper := fakeStakingKeeper{
|
||||
delegations: stakingtypes.Delegations{
|
||||
//bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[0],
|
||||
Shares: d("1"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[1],
|
||||
Shares: d("10"),
|
||||
},
|
||||
// not bonded
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[2],
|
||||
Shares: d("100"),
|
||||
},
|
||||
{
|
||||
DelegatorAddress: delegator,
|
||||
ValidatorAddress: validatorAddresses[3],
|
||||
Shares: d("1000"),
|
||||
},
|
||||
},
|
||||
validators: stakingtypes.Validators{
|
||||
unslashedBondedValidator(validatorAddresses[0]),
|
||||
unslashedBondedValidator(validatorAddresses[1]),
|
||||
unslashedNotBondedValidator(validatorAddresses[2]),
|
||||
unslashedNotBondedValidator(validatorAddresses[3]),
|
||||
},
|
||||
}
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper)
|
||||
|
||||
suite.Equal(
|
||||
d("111"),
|
||||
suite.keeper.GetTotalDelegated(suite.ctx, delegator, validatorAddresses[2], true),
|
||||
)
|
||||
}
|
@ -80,24 +80,20 @@ func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.
|
||||
// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim
|
||||
// by creating the claim and setting the supply reward factor index
|
||||
func (k Keeper) InitializeHardSupplyReward(ctx sdk.Context, deposit hardtypes.Deposit) {
|
||||
var supplyRewardIndexes types.MultiRewardIndexes
|
||||
for _, coin := range deposit.Amount {
|
||||
globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom)
|
||||
var multiRewardIndex types.MultiRewardIndex
|
||||
if foundGlobalRewardIndexes {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, globalRewardIndexes)
|
||||
} else {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(coin.Denom, types.RewardIndexes{})
|
||||
}
|
||||
supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex)
|
||||
}
|
||||
|
||||
claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
|
||||
if !found {
|
||||
// Instantiate claim object
|
||||
claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil)
|
||||
}
|
||||
|
||||
var supplyRewardIndexes types.MultiRewardIndexes
|
||||
for _, coin := range deposit.Amount {
|
||||
globalRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, coin.Denom)
|
||||
if !found {
|
||||
globalRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
supplyRewardIndexes = supplyRewardIndexes.With(coin.Denom, globalRewardIndexes)
|
||||
}
|
||||
|
||||
claim.SupplyRewardIndexes = supplyRewardIndexes
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
@ -111,50 +107,34 @@ func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.D
|
||||
}
|
||||
|
||||
for _, coin := range deposit.Amount {
|
||||
globalRewardIndexes, foundGlobalRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, coin.Denom)
|
||||
if !foundGlobalRewardIndexes {
|
||||
globalRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, coin.Denom)
|
||||
if !found {
|
||||
// The global factor is only not found if
|
||||
// - the supply denom has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
|
||||
// - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist)
|
||||
// If not found we could either skip this sync, or assume the global factor is zero.
|
||||
// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
|
||||
// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
|
||||
continue
|
||||
}
|
||||
|
||||
userMultiRewardIndex, foundUserMultiRewardIndex := claim.SupplyRewardIndexes.GetRewardIndex(coin.Denom)
|
||||
if !foundUserMultiRewardIndex {
|
||||
continue
|
||||
userRewardIndexes, found := claim.SupplyRewardIndexes.Get(coin.Denom)
|
||||
if !found {
|
||||
// Normally the reward indexes should always be found.
|
||||
// But if a denom was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that supplied denom.
|
||||
// So given the reward period was just added, assume the starting value for any global reward indexes, which is an empty slice.
|
||||
userRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
|
||||
userRewardIndexIndex, foundUserRewardIndexIndex := claim.SupplyRewardIndexes.GetRewardIndexIndex(coin.Denom)
|
||||
if !foundUserRewardIndexIndex {
|
||||
continue
|
||||
newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, coin.Amount.ToDec())
|
||||
if err != nil {
|
||||
// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
|
||||
// This panics if a global reward factor decreases or disappears between the old and new indexes.
|
||||
panic(fmt.Sprintf("corrupted global reward indexes found: %v", err))
|
||||
}
|
||||
|
||||
for _, globalRewardIndex := range globalRewardIndexes {
|
||||
userRewardIndex, foundUserRewardIndex := userMultiRewardIndex.RewardIndexes.GetRewardIndex(globalRewardIndex.CollateralType)
|
||||
if !foundUserRewardIndex {
|
||||
// User deposited this coin type before it had rewards. When new rewards are added, legacy depositors
|
||||
// should immediately begin earning rewards. Enable users to do so by updating their claim with the global
|
||||
// reward index denom and start their reward factor at 0.0
|
||||
userRewardIndex = types.NewRewardIndex(globalRewardIndex.CollateralType, sdk.ZeroDec())
|
||||
userMultiRewardIndex.RewardIndexes = append(userMultiRewardIndex.RewardIndexes, userRewardIndex)
|
||||
claim.SupplyRewardIndexes[userRewardIndexIndex] = userMultiRewardIndex
|
||||
}
|
||||
|
||||
globalRewardFactor := globalRewardIndex.RewardFactor
|
||||
userRewardFactor := userRewardIndex.RewardFactor
|
||||
rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsNegative() {
|
||||
panic(fmt.Sprintf("reward accumulation factor cannot be negative: %s", rewardsAccumulatedFactor))
|
||||
}
|
||||
|
||||
newRewardsAmount := rewardsAccumulatedFactor.Mul(deposit.Amount.AmountOf(coin.Denom).ToDec()).RoundInt()
|
||||
|
||||
factorIndex, foundFactorIndex := userMultiRewardIndex.RewardIndexes.GetFactorIndex(globalRewardIndex.CollateralType)
|
||||
if !foundFactorIndex { // should never trigger, as we basically do this check at the start of this loop
|
||||
continue
|
||||
}
|
||||
claim.SupplyRewardIndexes[userRewardIndexIndex].RewardIndexes[factorIndex].RewardFactor = globalRewardIndex.RewardFactor
|
||||
|
||||
newRewardsCoin := sdk.NewCoin(userRewardIndex.CollateralType, newRewardsAmount)
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
}
|
||||
claim.Reward = claim.Reward.Add(newRewards...)
|
||||
claim.SupplyRewardIndexes = claim.SupplyRewardIndexes.With(coin.Denom, globalRewardIndexes)
|
||||
}
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
@ -169,26 +149,22 @@ func (k Keeper) UpdateHardSupplyIndexDenoms(ctx sdk.Context, deposit hardtypes.D
|
||||
depositDenoms := getDenoms(deposit.Amount)
|
||||
supplyRewardIndexDenoms := claim.SupplyRewardIndexes.GetCollateralTypes()
|
||||
|
||||
uniqueDepositDenoms := setDifference(depositDenoms, supplyRewardIndexDenoms)
|
||||
uniqueSupplyRewardDenoms := setDifference(supplyRewardIndexDenoms, depositDenoms)
|
||||
|
||||
supplyRewardIndexes := claim.SupplyRewardIndexes
|
||||
|
||||
// Create a new multi-reward index in the claim for every new deposit denom
|
||||
uniqueDepositDenoms := setDifference(depositDenoms, supplyRewardIndexDenoms)
|
||||
|
||||
for _, denom := range uniqueDepositDenoms {
|
||||
_, foundUserRewardIndexes := claim.SupplyRewardIndexes.GetRewardIndex(denom)
|
||||
if !foundUserRewardIndexes {
|
||||
globalSupplyRewardIndexes, foundGlobalSupplyRewardIndexes := k.GetHardSupplyRewardIndexes(ctx, denom)
|
||||
var multiRewardIndex types.MultiRewardIndex
|
||||
if foundGlobalSupplyRewardIndexes {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(denom, globalSupplyRewardIndexes)
|
||||
} else {
|
||||
multiRewardIndex = types.NewMultiRewardIndex(denom, types.RewardIndexes{})
|
||||
}
|
||||
supplyRewardIndexes = append(supplyRewardIndexes, multiRewardIndex)
|
||||
globalSupplyRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, denom)
|
||||
if !found {
|
||||
globalSupplyRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
supplyRewardIndexes = supplyRewardIndexes.With(denom, globalSupplyRewardIndexes)
|
||||
}
|
||||
|
||||
// Delete multi-reward index from claim if the collateral type is no longer deposited
|
||||
uniqueSupplyRewardDenoms := setDifference(supplyRewardIndexDenoms, depositDenoms)
|
||||
|
||||
for _, denom := range uniqueSupplyRewardDenoms {
|
||||
supplyRewardIndexes = supplyRewardIndexes.RemoveRewardIndex(denom)
|
||||
}
|
||||
|
89
x/incentive/keeper/rewards_supply_init_test.go
Normal file
89
x/incentive/keeper/rewards_supply_init_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// InitializeHardSupplyRewardTests runs unit tests for the keeper.InitializeHardSupplyReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.SupplyRewardIndexes)
|
||||
// - global indexes in store
|
||||
// - deposit function arg (only deposit.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets or creates a claim
|
||||
type InitializeHardSupplyRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestInitializeHardSupplyReward(t *testing.T) {
|
||||
suite.Run(t, new(InitializeHardSupplyRewardTests))
|
||||
}
|
||||
|
||||
func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimExists() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
// Indexes should always be empty when initialize is called.
|
||||
// If initialize is called then the user must have repaid their deposit positions,
|
||||
// which means UpdateHardSupplyIndexDenoms was called and should have remove indexes.
|
||||
SupplyRewardIndexes: types.MultiRewardIndexes{},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimDoesNotExist() {
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, owner)
|
||||
suite.True(found)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetEmptyForMissingIndexes() {
|
||||
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
// Supply a denom that is not in the global indexes.
|
||||
// This happens when a deposit denom has no rewards associated with it.
|
||||
expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
|
||||
depositedDenoms := extractCollateralTypes(expectedIndexes)
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: owner,
|
||||
Amount: arbitraryCoinsWithDenoms(depositedDenoms...),
|
||||
}
|
||||
|
||||
suite.keeper.InitializeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
303
x/incentive/keeper/rewards_supply_sync_test.go
Normal file
303
x/incentive/keeper/rewards_supply_sync_test.go
Normal file
@ -0,0 +1,303 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// SynchronizeHardSupplyRewardTests runs unit tests for the keeper.SynchronizeHardSupplyReward method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store (only claim.SupplyRewardIndexes, claim.Reward)
|
||||
// - global indexes in store
|
||||
// - deposit function arg (only deposit.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets a claim
|
||||
type SynchronizeHardSupplyRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestSynchronizeHardSupplyReward(t *testing.T) {
|
||||
suite.Run(t, new(SynchronizeHardSupplyRewardTests))
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenGlobalIndexesHaveIncreased() {
|
||||
// This is the normal case
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(claim.SupplyRewardIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUnchangedWhenGlobalIndexesUnchanged() {
|
||||
// It should be safe to call SynchronizeHardSupplyReward multiple times
|
||||
|
||||
unchangingIndexes := nonEmptyMultiRewardIndexes
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: unchangingIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalSupplyIndexes(unchangingIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(unchangingIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(unchangingIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNewRewardAdded() {
|
||||
// When a new reward is added (via gov) for a hard deposit denom the user has already deposited, and the claim is synced;
|
||||
// Then the new reward's index should be added to the claim.
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNewRewardDenomAdded() {
|
||||
// When a new reward coin is added (via gov) to an already rewarded deposit denom (that the user has already deposited), and the claim is synced;
|
||||
// Then the new reward coin's index should be added to the claim.
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenGlobalIndexesHaveIncreased() {
|
||||
// This is the normal case
|
||||
// Given some time has passed (meaning the global indexes have increased)
|
||||
// When the claim is synced
|
||||
// The user earns rewards for the time passed
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
SupplyRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "depositdenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalSupplyIndexes(types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "depositdenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: cs(c("depositdenom", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
// new reward is (new index - old index) * deposit amount
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("rewarddenom", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRewardAdded() {
|
||||
// When a new reward is added (via gov) for a hard deposit denom the user has already deposited, and the claim is synced
|
||||
// Then the user earns rewards for the time since the reward was added
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
SupplyRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CollateralType: "newlyrewarded",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "otherreward",
|
||||
// Indexes start at 0 when the reward is added by gov,
|
||||
// so this represents the syncing happening some time later.
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: cs(c("rewarded", 1e9), c("newlyrewarded", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
// new reward is (new index - old index) * deposit amount for each deposited denom
|
||||
// The old index for `newlyrewarded` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("otherreward", 1_000_001_000_000), c("reward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRewardDenomAdded() {
|
||||
// When a new reward coin is added (via gov) to an already rewarded deposit denom (that the user has already deposited), and the claim is synced;
|
||||
// Then the user earns rewards for the time since the reward was added
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
SupplyRewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "deposited",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "deposited",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
{
|
||||
CollateralType: "otherreward",
|
||||
// Indexes start at 0 when the reward is added by gov,
|
||||
// so this represents the syncing happening some time later.
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: cs(c("deposited", 1e9)),
|
||||
}
|
||||
|
||||
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
|
||||
|
||||
// new reward is (new index - old index) * deposit amount for each deposited denom
|
||||
// The old index for `otherreward` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(
|
||||
cs(c("reward", 1_000_001_000_000), c("otherreward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
@ -61,7 +61,7 @@ func (suite *SupplyRewardsTestSuite) SetupWithGenState(authBuilder app.AuthGenes
|
||||
authBuilder.BuildMarshalled(),
|
||||
NewPricefeedGenStateMultiFromTime(suite.genesisTime),
|
||||
hardBuilder.BuildMarshalled(),
|
||||
NewCommitteeGenesisState(suite.addrs[:2]), // TODO add committee members to suite
|
||||
NewCommitteeGenesisState(suite.addrs[:2]),
|
||||
incentBuilder.BuildMarshalled(),
|
||||
)
|
||||
}
|
||||
@ -498,7 +498,6 @@ func (suite *SupplyRewardsTestSuite) TestSynchronizeHardSupplyReward() {
|
||||
updatedTimeDuration: 86400,
|
||||
},
|
||||
},
|
||||
// TODO test synchronize when there is a reward period with 0 rewardsPerSecond
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
|
119
x/incentive/keeper/rewards_supply_update_test.go
Normal file
119
x/incentive/keeper/rewards_supply_update_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// UpdateHardSupplyIndexDenomsTests runs unit tests for the keeper.UpdateHardSupplyIndexDenoms method
|
||||
//
|
||||
// inputs
|
||||
// - claim in store if it exists (only claim.SupplyRewardIndexes)
|
||||
// - global indexes in store
|
||||
// - deposit function arg (only deposit.Amount)
|
||||
//
|
||||
// outputs
|
||||
// - sets a claim
|
||||
type UpdateHardSupplyIndexDenomsTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestUpdateHardSupplyIndexDenoms(t *testing.T) {
|
||||
suite.Run(t, new(UpdateHardSupplyIndexDenomsTests))
|
||||
}
|
||||
|
||||
func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreRemovedForDenomsNoLongerSupplied() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
suite.storeGlobalSupplyIndexes(claim.SupplyRewardIndexes)
|
||||
|
||||
// remove one denom from the indexes already in the deposit
|
||||
expectedIndexes := claim.SupplyRewardIndexes[1:]
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(expectedIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreAddedForNewlySuppliedDenoms() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(globalIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreUnchangedWhenSuppliedDenomsUnchanged() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
// Set global indexes with same denoms but different values.
|
||||
// UpdateHardSupplyIndexDenoms should ignore the new values.
|
||||
suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes))
|
||||
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(extractCollateralTypes(claim.SupplyRewardIndexes)...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(claim.SupplyRewardIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *UpdateHardSupplyIndexDenomsTests) TestEmptyClaimIndexesAreAddedForNewlySuppliedButNotRewardedDenoms() {
|
||||
claim := types.HardLiquidityProviderClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
SupplyRewardIndexes: nonEmptyMultiRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
suite.storeGlobalSupplyIndexes(claim.SupplyRewardIndexes)
|
||||
|
||||
// add a denom to the deposited amount that is not in the global or claim's indexes
|
||||
expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.SupplyRewardIndexes)
|
||||
depositedDenoms := extractCollateralTypes(expectedIndexes)
|
||||
deposit := hardtypes.Deposit{
|
||||
Depositor: claim.Owner,
|
||||
Amount: arbitraryCoinsWithDenoms(depositedDenoms...),
|
||||
}
|
||||
|
||||
suite.keeper.UpdateHardSupplyIndexDenoms(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(expectedIndexes, syncedClaim.SupplyRewardIndexes)
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
@ -55,66 +57,61 @@ func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
// this collateral type is not incentivized, do nothing
|
||||
return
|
||||
}
|
||||
rewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
rewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
|
||||
if !found { // this is the owner's first usdx minting reward claim
|
||||
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)})
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
return
|
||||
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{})
|
||||
}
|
||||
// the owner has an existing usdx minting reward claim
|
||||
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
|
||||
if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
|
||||
claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, rewardFactor))
|
||||
} else { // the owner has a previous usdx minting reward for this collateral type
|
||||
claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor)
|
||||
}
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value.
|
||||
// this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module
|
||||
func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
_, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type)
|
||||
if !found {
|
||||
// this collateral type is not incentivized, do nothing
|
||||
return
|
||||
}
|
||||
|
||||
globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
globalRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
claim.RewardIndexes = claim.RewardIndexes.With(cdp.Type, globalRewardFactor)
|
||||
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value.
|
||||
// this should be called before a cdp is modified.
|
||||
func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
|
||||
globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
// The global factor is only not found if
|
||||
// - the cdp collateral type has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit)
|
||||
// - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist)
|
||||
// If not found we could either skip this sync, or assume the global factor is zero.
|
||||
// Skipping will avoid storing unnecessary factors in the claim for non rewarded denoms.
|
||||
// And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards.
|
||||
return
|
||||
}
|
||||
claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
|
||||
if !found {
|
||||
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)})
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
return
|
||||
claim = types.NewUSDXMintingClaim(
|
||||
cdp.Owner,
|
||||
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()),
|
||||
types.RewardIndexes{},
|
||||
)
|
||||
}
|
||||
|
||||
// the owner has an existing usdx minting reward claim
|
||||
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
|
||||
if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
|
||||
claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor))
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
return
|
||||
userRewardFactor, found := claim.RewardIndexes.Get(cdp.Type)
|
||||
if !found {
|
||||
// Normally the factor should always be found, as it is added when the cdp is created in InitializeUSDXMintingClaim.
|
||||
// However if a cdp type is not rewarded then becomes rewarded (ie a reward period is added to params), existing cdps will not have the factor in their claims.
|
||||
// So assume the factor is the starting value for any global factor: 0.
|
||||
userRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
userRewardFactor := claim.RewardIndexes[index].RewardFactor
|
||||
rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsZero() {
|
||||
return
|
||||
}
|
||||
claim.RewardIndexes[index].RewardFactor = globalRewardFactor
|
||||
newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt()
|
||||
if newRewardsAmount.IsZero() {
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
return
|
||||
|
||||
newRewardsAmount, err := k.CalculateSingleReward(userRewardFactor, globalRewardFactor, cdp.GetTotalPrincipal().Amount.ToDec())
|
||||
if err != nil {
|
||||
// Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards.
|
||||
// This panics if a global reward factor decreases or disappears between the old and new indexes.
|
||||
panic(fmt.Sprintf("corrupted global reward indexes found: %v", err))
|
||||
}
|
||||
newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount)
|
||||
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
claim.RewardIndexes = claim.RewardIndexes.With(cdp.Type, globalRewardFactor)
|
||||
|
||||
k.SetUSDXMintingClaim(ctx, claim)
|
||||
}
|
||||
|
||||
|
311
x/incentive/keeper/rewards_usdx_unit_test.go
Normal file
311
x/incentive/keeper/rewards_usdx_unit_test.go
Normal file
@ -0,0 +1,311 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// usdxRewardsUnitTester contains common methods for running unit tests for keeper methods related to the USDX minting rewards
|
||||
type usdxRewardsUnitTester struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func (suite *usdxRewardsUnitTester) storeGlobalUSDXIndexes(indexes types.RewardIndexes) {
|
||||
for _, ri := range indexes {
|
||||
suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, ri.CollateralType, ri.RewardFactor)
|
||||
}
|
||||
}
|
||||
func (suite *usdxRewardsUnitTester) storeClaim(claim types.USDXMintingClaim) {
|
||||
suite.keeper.SetUSDXMintingClaim(suite.ctx, claim)
|
||||
}
|
||||
|
||||
type InitializeUSDXMintingClaimTests struct {
|
||||
usdxRewardsUnitTester
|
||||
}
|
||||
|
||||
func TestInitializeUSDXMintingClaims(t *testing.T) {
|
||||
suite.Run(t, new(InitializeUSDXMintingClaimTests))
|
||||
}
|
||||
|
||||
func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimDoesNotExist() {
|
||||
collateralType := "bnb-a"
|
||||
|
||||
subspace := paramsWithSingleUSDXRewardPeriod(collateralType)
|
||||
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil)
|
||||
|
||||
cdp := NewCDPBuilder(arbitraryAddress(), collateralType).Build()
|
||||
|
||||
globalIndexes := types.RewardIndexes{{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.2"),
|
||||
}}
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
suite.keeper.InitializeUSDXMintingClaim(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, f := suite.keeper.GetUSDXMintingClaim(suite.ctx, cdp.Owner)
|
||||
suite.True(f)
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *InitializeUSDXMintingClaimTests) TestClaimIndexIsSetWhenClaimExists() {
|
||||
collateralType := "bnb-a"
|
||||
|
||||
subspace := paramsWithSingleUSDXRewardPeriod(collateralType)
|
||||
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil)
|
||||
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
},
|
||||
RewardIndexes: types.RewardIndexes{{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.1"),
|
||||
}},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.RewardIndexes{{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.2"),
|
||||
}}
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(claim.Owner, collateralType).Build()
|
||||
|
||||
suite.keeper.InitializeUSDXMintingClaim(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, cdp.Owner)
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
}
|
||||
|
||||
type SynchronizeUSDXMintingRewardTests struct {
|
||||
usdxRewardsUnitTester
|
||||
}
|
||||
|
||||
func TestSynchronizeUSDXMintingReward(t *testing.T) {
|
||||
suite.Run(t, new(SynchronizeUSDXMintingRewardTests))
|
||||
}
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestRewardUnchangedWhenGlobalIndexesUnchanged() {
|
||||
unchangingRewardIndexes := nonEmptyRewardIndexes
|
||||
collateralType := extractFirstCollateralType(unchangingRewardIndexes)
|
||||
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: c(types.USDXMintingRewardDenom, 0),
|
||||
},
|
||||
RewardIndexes: unchangingRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
suite.storeGlobalUSDXIndexes(unchangingRewardIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(claim.Owner, collateralType).WithPrincipal(i(1e12)).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(claim.Reward, syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestRewardIsIncrementedWhenGlobalIndexIncreased() {
|
||||
collateralType := "bnb-a"
|
||||
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: c(types.USDXMintingRewardDenom, 0),
|
||||
},
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.2"),
|
||||
},
|
||||
}
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(claim.Owner, collateralType).WithPrincipal(i(1e12)).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, claim.Owner)
|
||||
// reward is ( new index - old index ) * cdp.TotalPrincipal
|
||||
suite.Equal(c(types.USDXMintingRewardDenom, 1e11), syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestRewardIsIncrementedWhenNewRewardAddedAndClaimDoesNotExit() {
|
||||
collateralType := "bnb-a"
|
||||
|
||||
globalIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: d("0.2"),
|
||||
},
|
||||
}
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(arbitraryAddress(), collateralType).WithPrincipal(i(1e12)).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, cdp.Owner)
|
||||
// The global index was not around when this cdp was created as it was not stored in a claim.
|
||||
// Therefore it must have been added via params after.
|
||||
// To include rewards since the params were updated, the old index should be assumed to be 0.
|
||||
// reward is ( new index - old index ) * cdp.TotalPrincipal
|
||||
suite.Equal(c(types.USDXMintingRewardDenom, 2e11), syncedClaim.Reward)
|
||||
}
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIndexIsUpdatedWhenGlobalIndexIncreased() {
|
||||
claimsRewardIndexes := nonEmptyRewardIndexes
|
||||
collateralType := extractFirstCollateralType(claimsRewardIndexes)
|
||||
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: c(types.USDXMintingRewardDenom, 0),
|
||||
},
|
||||
RewardIndexes: claimsRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := increaseRewardFactors(claimsRewardIndexes)
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(claim.Owner, collateralType).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, claim.Owner)
|
||||
|
||||
// Only the claim's index for `collateralType` should have been changed
|
||||
i, _ := globalIndexes.Get(collateralType)
|
||||
expectedIndexes := claimsRewardIndexes.With(collateralType, i)
|
||||
suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIndexIsUpdatedWhenNewRewardAddedAndClaimAlreadyExists() {
|
||||
claimsRewardIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "bnb-a",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
{
|
||||
CollateralType: "busd-b",
|
||||
RewardFactor: d("0.4"),
|
||||
},
|
||||
}
|
||||
newRewardIndex := types.NewRewardIndex("xrp-a", d("0.0001"))
|
||||
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: c(types.USDXMintingRewardDenom, 0),
|
||||
},
|
||||
RewardIndexes: claimsRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
|
||||
globalIndexes := increaseRewardFactors(claimsRewardIndexes)
|
||||
globalIndexes = append(globalIndexes, newRewardIndex)
|
||||
suite.storeGlobalUSDXIndexes(globalIndexes)
|
||||
|
||||
cdp := NewCDPBuilder(claim.Owner, newRewardIndex.CollateralType).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, claim.Owner)
|
||||
|
||||
// Only the claim's index for `collateralType` should have been changed
|
||||
expectedIndexes := claimsRewardIndexes.With(newRewardIndex.CollateralType, newRewardIndex.RewardFactor)
|
||||
suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeUSDXMintingRewardTests) TestClaimIsUnchangedWhenGlobalFactorMissing() {
|
||||
claimsRewardIndexes := nonEmptyRewardIndexes
|
||||
claim := types.USDXMintingClaim{
|
||||
BaseClaim: types.BaseClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: c(types.USDXMintingRewardDenom, 0),
|
||||
},
|
||||
RewardIndexes: claimsRewardIndexes,
|
||||
}
|
||||
suite.storeClaim(claim)
|
||||
// don't store any reward indexes
|
||||
|
||||
// create a cdp with collateral type that doesn't exist in the claim's indexes, and does not have a corresponding global factor
|
||||
cdp := NewCDPBuilder(claim.Owner, "unrewardedcollateral").WithPrincipal(i(1e12)).Build()
|
||||
|
||||
suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetUSDXMintingClaim(suite.ctx, claim.Owner)
|
||||
suite.Equal(claim.RewardIndexes, syncedClaim.RewardIndexes)
|
||||
suite.Equal(claim.Reward, syncedClaim.Reward)
|
||||
}
|
||||
|
||||
type cdpBuilder struct {
|
||||
cdptypes.CDP
|
||||
}
|
||||
|
||||
func NewCDPBuilder(owner sdk.AccAddress, collateralType string) cdpBuilder {
|
||||
return cdpBuilder{
|
||||
CDP: cdptypes.CDP{
|
||||
Owner: owner,
|
||||
Type: collateralType,
|
||||
// The zero value of Principal and AccumulatedFees (type sdk.Coin) is invalid as the denom is ""
|
||||
// Set them to the default denom, but with 0 amount.
|
||||
Principal: c(cdptypes.DefaultStableDenom, 0),
|
||||
AccumulatedFees: c(cdptypes.DefaultStableDenom, 0),
|
||||
}}
|
||||
}
|
||||
|
||||
func (builder cdpBuilder) Build() cdptypes.CDP { return builder.CDP }
|
||||
|
||||
func (builder cdpBuilder) WithPrincipal(principal sdk.Int) cdpBuilder {
|
||||
builder.Principal = sdk.NewCoin(cdptypes.DefaultStableDenom, principal)
|
||||
return builder
|
||||
}
|
||||
|
||||
var nonEmptyRewardIndexes = types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "bnb-a",
|
||||
RewardFactor: d("0.1"),
|
||||
},
|
||||
{
|
||||
CollateralType: "busd-b",
|
||||
RewardFactor: d("0.4"),
|
||||
},
|
||||
}
|
||||
|
||||
func paramsWithSingleUSDXRewardPeriod(collateralType string) types.ParamSubspace {
|
||||
return &fakeParamSubspace{
|
||||
params: types.Params{
|
||||
USDXMintingRewardPeriods: types.RewardPeriods{
|
||||
{
|
||||
CollateralType: collateralType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func extractFirstCollateralType(indexes types.RewardIndexes) string {
|
||||
if len(indexes) == 0 {
|
||||
panic("cannot extract a collateral type from 0 length RewardIndexes")
|
||||
}
|
||||
return indexes[0].CollateralType
|
||||
}
|
248
x/incentive/keeper/unit_test.go
Normal file
248
x/incentive/keeper/unit_test.go
Normal file
@ -0,0 +1,248 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/stretchr/testify/suite"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
db "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// NewTestContext sets up a basic context with an in-memory db
|
||||
func NewTestContext(requiredStoreKeys ...sdk.StoreKey) sdk.Context {
|
||||
memDB := db.NewMemDB()
|
||||
cms := store.NewCommitMultiStore(memDB)
|
||||
|
||||
for _, key := range requiredStoreKeys {
|
||||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil)
|
||||
}
|
||||
|
||||
cms.LoadLatestVersion()
|
||||
|
||||
return sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger())
|
||||
}
|
||||
|
||||
// unitTester is a wrapper around suite.Suite, with common functionality for keeper unit tests.
|
||||
// It can be embedded in structs the same way as suite.Suite.
|
||||
type unitTester struct {
|
||||
suite.Suite
|
||||
keeper keeper.Keeper
|
||||
ctx sdk.Context
|
||||
|
||||
cdc *codec.Codec
|
||||
incentiveStoreKey sdk.StoreKey
|
||||
}
|
||||
|
||||
func (suite *unitTester) SetupSuite() {
|
||||
suite.cdc = app.MakeCodec()
|
||||
|
||||
suite.incentiveStoreKey = sdk.NewKVStoreKey(types.StoreKey)
|
||||
|
||||
}
|
||||
|
||||
func (suite *unitTester) SetupTest() {
|
||||
suite.ctx = NewTestContext(suite.incentiveStoreKey)
|
||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (suite *unitTester) TearDownTest() {
|
||||
suite.keeper = keeper.Keeper{}
|
||||
suite.ctx = sdk.Context{}
|
||||
}
|
||||
|
||||
func (suite *unitTester) NewKeeper(paramSubspace types.ParamSubspace, sk types.SupplyKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper) keeper.Keeper {
|
||||
return keeper.NewKeeper(suite.cdc, suite.incentiveStoreKey, paramSubspace, sk, cdpk, hk, ak, stk)
|
||||
}
|
||||
|
||||
func (suite *unitTester) storeGlobalBorrowIndexes(indexes types.MultiRewardIndexes) {
|
||||
for _, i := range indexes {
|
||||
suite.keeper.SetHardBorrowRewardIndexes(suite.ctx, i.CollateralType, i.RewardIndexes)
|
||||
}
|
||||
}
|
||||
func (suite *unitTester) storeGlobalSupplyIndexes(indexes types.MultiRewardIndexes) {
|
||||
for _, i := range indexes {
|
||||
suite.keeper.SetHardSupplyRewardIndexes(suite.ctx, i.CollateralType, i.RewardIndexes)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *unitTester) storeClaim(claim types.HardLiquidityProviderClaim) {
|
||||
suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, claim)
|
||||
}
|
||||
|
||||
type fakeParamSubspace struct {
|
||||
params types.Params
|
||||
}
|
||||
|
||||
func (subspace *fakeParamSubspace) GetParamSet(_ sdk.Context, ps params.ParamSet) {
|
||||
*(ps.(*types.Params)) = subspace.params
|
||||
}
|
||||
func (subspace *fakeParamSubspace) SetParamSet(_ sdk.Context, ps params.ParamSet) {
|
||||
subspace.params = *(ps.(*types.Params))
|
||||
}
|
||||
func (subspace *fakeParamSubspace) HasKeyTable() bool {
|
||||
// return true so the keeper does no try to set the key table, which does nothing
|
||||
return true
|
||||
}
|
||||
func (subspace *fakeParamSubspace) WithKeyTable(params.KeyTable) params.Subspace {
|
||||
// return an non-functional subspace to satisfy the interface
|
||||
return params.Subspace{}
|
||||
}
|
||||
|
||||
func arbitraryCoin() sdk.Coin {
|
||||
return c("hard", 1e9)
|
||||
}
|
||||
|
||||
func arbitraryCoins() sdk.Coins {
|
||||
return cs(c("btcb", 1))
|
||||
}
|
||||
|
||||
func arbitraryCoinsWithDenoms(denom ...string) sdk.Coins {
|
||||
const arbitraryAmount = 1 // must be > 0 as sdk.Coins type only stores positive amounts
|
||||
coins := sdk.NewCoins()
|
||||
for _, d := range denom {
|
||||
coins = coins.Add(sdk.NewInt64Coin(d, arbitraryAmount))
|
||||
}
|
||||
return coins
|
||||
}
|
||||
|
||||
func arbitraryAddress() sdk.AccAddress {
|
||||
_, addresses := app.GeneratePrivKeyAddressPairs(1)
|
||||
return addresses[0]
|
||||
}
|
||||
func arbitraryValidatorAddress() sdk.ValAddress {
|
||||
return generateValidatorAddresses(1)[0]
|
||||
}
|
||||
func generateValidatorAddresses(n int) []sdk.ValAddress {
|
||||
_, addresses := app.GeneratePrivKeyAddressPairs(n)
|
||||
var valAddresses []sdk.ValAddress
|
||||
for _, a := range addresses {
|
||||
valAddresses = append(valAddresses, sdk.ValAddress(a))
|
||||
}
|
||||
return valAddresses
|
||||
}
|
||||
|
||||
var nonEmptyMultiRewardIndexes = types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "bnb",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.02"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.04"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CollateralType: "btcb",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.2"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func extractCollateralTypes(indexes types.MultiRewardIndexes) []string {
|
||||
var denoms []string
|
||||
for _, ri := range indexes {
|
||||
denoms = append(denoms, ri.CollateralType)
|
||||
}
|
||||
return denoms
|
||||
}
|
||||
|
||||
func increaseAllRewardFactors(indexes types.MultiRewardIndexes) types.MultiRewardIndexes {
|
||||
increasedIndexes := make(types.MultiRewardIndexes, len(indexes))
|
||||
copy(increasedIndexes, indexes)
|
||||
|
||||
for i := range increasedIndexes {
|
||||
increasedIndexes[i].RewardIndexes = increaseRewardFactors(increasedIndexes[i].RewardIndexes)
|
||||
}
|
||||
return increasedIndexes
|
||||
}
|
||||
|
||||
func increaseRewardFactors(indexes types.RewardIndexes) types.RewardIndexes {
|
||||
increasedIndexes := make(types.RewardIndexes, len(indexes))
|
||||
copy(increasedIndexes, indexes)
|
||||
|
||||
for i := range increasedIndexes {
|
||||
increasedIndexes[i].RewardFactor = increasedIndexes[i].RewardFactor.MulInt64(2)
|
||||
}
|
||||
return increasedIndexes
|
||||
}
|
||||
|
||||
func appendUniqueMultiRewardIndex(indexes types.MultiRewardIndexes) types.MultiRewardIndexes {
|
||||
const uniqueDenom = "uniquedenom"
|
||||
|
||||
for _, mri := range indexes {
|
||||
if mri.CollateralType == uniqueDenom {
|
||||
panic(fmt.Sprintf("tried to add unique multi reward index with denom '%s', but denom already existed", uniqueDenom))
|
||||
}
|
||||
}
|
||||
|
||||
return append(indexes, types.NewMultiRewardIndex(
|
||||
uniqueDenom,
|
||||
types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "hard",
|
||||
RewardFactor: d("0.02"),
|
||||
},
|
||||
{
|
||||
CollateralType: "ukava",
|
||||
RewardFactor: d("0.04"),
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func appendUniqueEmptyMultiRewardIndex(indexes types.MultiRewardIndexes) types.MultiRewardIndexes {
|
||||
const uniqueDenom = "uniquedenom"
|
||||
|
||||
for _, mri := range indexes {
|
||||
if mri.CollateralType == uniqueDenom {
|
||||
panic(fmt.Sprintf("tried to add unique multi reward index with denom '%s', but denom already existed", uniqueDenom))
|
||||
}
|
||||
}
|
||||
|
||||
return append(indexes, types.NewMultiRewardIndex(uniqueDenom, nil))
|
||||
}
|
||||
|
||||
func appendUniqueRewardIndexToFirstItem(indexes types.MultiRewardIndexes) types.MultiRewardIndexes {
|
||||
newIndexes := make(types.MultiRewardIndexes, len(indexes))
|
||||
copy(newIndexes, indexes)
|
||||
|
||||
newIndexes[0].RewardIndexes = appendUniqueRewardIndex(newIndexes[0].RewardIndexes)
|
||||
return newIndexes
|
||||
}
|
||||
|
||||
func appendUniqueRewardIndex(indexes types.RewardIndexes) types.RewardIndexes {
|
||||
const uniqueDenom = "uniquereward"
|
||||
|
||||
for _, mri := range indexes {
|
||||
if mri.CollateralType == uniqueDenom {
|
||||
panic(fmt.Sprintf("tried to add unique reward index with denom '%s', but denom already existed", uniqueDenom))
|
||||
}
|
||||
}
|
||||
|
||||
return append(
|
||||
indexes,
|
||||
types.NewRewardIndex(uniqueDenom, d("0.02")),
|
||||
)
|
||||
}
|
@ -402,6 +402,30 @@ func (ris RewardIndexes) GetRewardIndex(denom string) (RewardIndex, bool) {
|
||||
return RewardIndex{}, false
|
||||
}
|
||||
|
||||
// Get fetches a RewardFactor by it's denom
|
||||
func (ris RewardIndexes) Get(denom string) (sdk.Dec, bool) {
|
||||
for _, ri := range ris {
|
||||
if ri.CollateralType == denom {
|
||||
return ri.RewardFactor, true
|
||||
}
|
||||
}
|
||||
return sdk.Dec{}, false
|
||||
}
|
||||
|
||||
// With returns a copy of the indexes with a new reward factor added
|
||||
func (ris RewardIndexes) With(denom string, factor sdk.Dec) RewardIndexes {
|
||||
newIndexes := make(RewardIndexes, len(ris))
|
||||
copy(newIndexes, ris)
|
||||
|
||||
for i, ri := range newIndexes {
|
||||
if ri.CollateralType == denom {
|
||||
newIndexes[i].RewardFactor = factor
|
||||
return newIndexes
|
||||
}
|
||||
}
|
||||
return append(newIndexes, NewRewardIndex(denom, factor))
|
||||
}
|
||||
|
||||
// GetFactorIndex gets the index of a specific reward index inside the array by its index
|
||||
func (ris RewardIndexes) GetFactorIndex(denom string) (int, bool) {
|
||||
for i, ri := range ris {
|
||||
@ -476,6 +500,16 @@ func (mris MultiRewardIndexes) GetRewardIndex(denom string) (MultiRewardIndex, b
|
||||
return MultiRewardIndex{}, false
|
||||
}
|
||||
|
||||
// Get fetches a RewardIndexes by it's denom
|
||||
func (mris MultiRewardIndexes) Get(denom string) (RewardIndexes, bool) {
|
||||
for _, mri := range mris {
|
||||
if mri.CollateralType == denom {
|
||||
return mri.RewardIndexes, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetRewardIndexIndex fetches a specific reward index inside the array by its denom
|
||||
func (mris MultiRewardIndexes) GetRewardIndexIndex(denom string) (int, bool) {
|
||||
for i, ri := range mris {
|
||||
@ -486,6 +520,19 @@ func (mris MultiRewardIndexes) GetRewardIndexIndex(denom string) (int, bool) {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// With returns a copy of the indexes with a new RewardIndexes added
|
||||
func (mris MultiRewardIndexes) With(denom string, indexes RewardIndexes) MultiRewardIndexes {
|
||||
newIndexes := mris.copy()
|
||||
|
||||
for i, mri := range newIndexes {
|
||||
if mri.CollateralType == denom {
|
||||
newIndexes[i].RewardIndexes = indexes
|
||||
return newIndexes
|
||||
}
|
||||
}
|
||||
return append(newIndexes, NewMultiRewardIndex(denom, indexes))
|
||||
}
|
||||
|
||||
// GetCollateralTypes returns a slice of containing all collateral types
|
||||
func (mris MultiRewardIndexes) GetCollateralTypes() []string {
|
||||
var collateralTypes []string
|
||||
@ -499,7 +546,9 @@ func (mris MultiRewardIndexes) GetCollateralTypes() []string {
|
||||
func (mris MultiRewardIndexes) RemoveRewardIndex(denom string) MultiRewardIndexes {
|
||||
for i, ri := range mris {
|
||||
if ri.CollateralType == denom {
|
||||
return append(mris[:i], mris[i+1:]...)
|
||||
// copy the slice and underlying array to avoid altering the original
|
||||
copy := mris.copy()
|
||||
return append(copy[:i], copy[i+1:]...)
|
||||
}
|
||||
}
|
||||
return mris
|
||||
@ -514,3 +563,10 @@ func (mris MultiRewardIndexes) Validate() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy returns a copy of the slice and underlying array
|
||||
func (mris MultiRewardIndexes) copy() MultiRewardIndexes {
|
||||
newIndexes := make(MultiRewardIndexes, len(mris))
|
||||
copy(newIndexes, mris)
|
||||
return newIndexes
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func TestClaimsValidate(t *testing.T) {
|
||||
@ -72,3 +71,295 @@ func TestClaimsValidate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewardIndexes(t *testing.T) {
|
||||
t.Run("With", func(t *testing.T) {
|
||||
var arbitraryDec = sdk.MustNewDecFromStr("0.1")
|
||||
|
||||
type args struct {
|
||||
denom string
|
||||
factor sdk.Dec
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
rewardIndexes RewardIndexes
|
||||
args args
|
||||
expected RewardIndexes
|
||||
}{
|
||||
{
|
||||
name: "when index is not present, it's added and original isn't overwritten",
|
||||
rewardIndexes: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec),
|
||||
},
|
||||
args: args{
|
||||
denom: "otherdenom",
|
||||
factor: arbitraryDec,
|
||||
},
|
||||
expected: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec),
|
||||
NewRewardIndex("otherdenom", arbitraryDec),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when index is present, it's updated and original isn't overwritten",
|
||||
rewardIndexes: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec),
|
||||
},
|
||||
args: args{
|
||||
denom: "denom",
|
||||
factor: arbitraryDec.MulInt64(2),
|
||||
},
|
||||
expected: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec.MulInt64(2)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
newIndexes := tc.rewardIndexes.With(tc.args.denom, tc.args.factor)
|
||||
|
||||
require.Equal(t, tc.expected, newIndexes)
|
||||
require.NotEqual(t, tc.rewardIndexes, newIndexes) // check original slice not modified
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
var arbitraryDec = sdk.MustNewDecFromStr("0.1")
|
||||
|
||||
type expected struct {
|
||||
factor sdk.Dec
|
||||
found bool
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
rewardIndexes RewardIndexes
|
||||
arg_denom string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "when index is present, it is found and returned",
|
||||
rewardIndexes: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec),
|
||||
},
|
||||
arg_denom: "denom",
|
||||
expected: expected{
|
||||
factor: arbitraryDec,
|
||||
found: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when index is not present, it is not found",
|
||||
rewardIndexes: RewardIndexes{
|
||||
NewRewardIndex("denom", arbitraryDec),
|
||||
},
|
||||
arg_denom: "notpresent",
|
||||
expected: expected{
|
||||
found: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
factor, found := tc.rewardIndexes.Get(tc.arg_denom)
|
||||
|
||||
require.Equal(t, tc.expected.found, found)
|
||||
require.Equal(t, tc.expected.factor, factor)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultiRewardIndexes(t *testing.T) {
|
||||
arbitraryRewardIndexes := RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: sdk.MustNewDecFromStr("0.1"),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
type expected struct {
|
||||
rewardIndexes RewardIndexes
|
||||
found bool
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
multiRewardIndexes MultiRewardIndexes
|
||||
arg_denom string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "when indexes are present, they are found and returned",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
arg_denom: "denom",
|
||||
expected: expected{
|
||||
found: true,
|
||||
rewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when indexes are not present, they are not found",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
arg_denom: "notpresent",
|
||||
expected: expected{
|
||||
found: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rewardIndexes, found := tc.multiRewardIndexes.Get(tc.arg_denom)
|
||||
|
||||
require.Equal(t, tc.expected.found, found)
|
||||
require.Equal(t, tc.expected.rewardIndexes, rewardIndexes)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("With", func(t *testing.T) {
|
||||
type args struct {
|
||||
denom string
|
||||
rewardIndexes RewardIndexes
|
||||
}
|
||||
testcases := []struct {
|
||||
name string
|
||||
multiRewardIndexes MultiRewardIndexes
|
||||
args args
|
||||
expected MultiRewardIndexes
|
||||
}{
|
||||
{
|
||||
name: "when indexes are not present, add them and do not update original",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
denom: "otherdenom",
|
||||
rewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
expected: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
{
|
||||
CollateralType: "otherdenom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when indexes are present, update them and do not update original",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
denom: "denom",
|
||||
rewardIndexes: appendUniqueRewardIndex(arbitraryRewardIndexes),
|
||||
},
|
||||
expected: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: appendUniqueRewardIndex(arbitraryRewardIndexes),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
oldIndexes := tc.multiRewardIndexes.copy()
|
||||
|
||||
newIndexes := tc.multiRewardIndexes.With(tc.args.denom, tc.args.rewardIndexes)
|
||||
|
||||
require.Equal(t, tc.expected, newIndexes)
|
||||
require.Equal(t, oldIndexes, tc.multiRewardIndexes)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("RemoveRewardIndex", func(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
multiRewardIndexes MultiRewardIndexes
|
||||
arg_denom string
|
||||
expected MultiRewardIndexes
|
||||
}{
|
||||
{
|
||||
name: "when indexes are not present, do nothing",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
arg_denom: "notpresent",
|
||||
expected: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when indexes are present, remove them and do not update original",
|
||||
multiRewardIndexes: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "denom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
{
|
||||
CollateralType: "otherdenom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
arg_denom: "denom",
|
||||
expected: MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "otherdenom",
|
||||
RewardIndexes: arbitraryRewardIndexes,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
oldIndexes := tc.multiRewardIndexes.copy()
|
||||
|
||||
newIndexes := tc.multiRewardIndexes.RemoveRewardIndex(tc.arg_denom)
|
||||
|
||||
require.Equal(t, tc.expected, newIndexes)
|
||||
require.Equal(t, oldIndexes, tc.multiRewardIndexes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func appendUniqueRewardIndex(indexes RewardIndexes) RewardIndexes {
|
||||
const uniqueDenom = "uniquereward"
|
||||
|
||||
for _, mri := range indexes {
|
||||
if mri.CollateralType == uniqueDenom {
|
||||
panic(fmt.Sprintf("tried to add unique reward index with denom '%s', but denom already existed", uniqueDenom))
|
||||
}
|
||||
}
|
||||
|
||||
return append(
|
||||
indexes,
|
||||
NewRewardIndex(uniqueDenom, sdk.MustNewDecFromStr("0.02")),
|
||||
)
|
||||
}
|
||||
|
@ -19,4 +19,5 @@ var (
|
||||
ErrClaimExpired = sdkerrors.Register(ModuleName, 10, "claim has expired")
|
||||
ErrInvalidClaimType = sdkerrors.Register(ModuleName, 11, "invalid claim type")
|
||||
ErrInvalidClaimOwner = sdkerrors.Register(ModuleName, 12, "invalid claim owner")
|
||||
ErrDecreasingRewardFactor = sdkerrors.Register(ModuleName, 13, "found new reward factor less than an old reward factor")
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
|
||||
@ -10,6 +11,14 @@ import (
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
)
|
||||
|
||||
// ParamSubspace defines the expected Subspace interfacace
|
||||
type ParamSubspace interface {
|
||||
GetParamSet(sdk.Context, params.ParamSet)
|
||||
SetParamSet(sdk.Context, params.ParamSet)
|
||||
WithKeyTable(params.KeyTable) params.Subspace
|
||||
HasKeyTable() bool
|
||||
}
|
||||
|
||||
// SupplyKeeper defines the expected supply keeper for module accounts
|
||||
type SupplyKeeper interface {
|
||||
GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI
|
||||
|
Loading…
Reference in New Issue
Block a user