fix: Use correct share values for earn incentive hooks (#1347)

* Call earn hooks with correct share values

User's shares instead of total vault shares

* Use different amounts for different accounts

* Add claim test with staking claims

* Remove logging statements

* Remove log

* Pass suite.T() to mock earn hooks

* Use begin blocker for staking reward distribution

* Remove unused beginblocker

* Remove log
This commit is contained in:
Derrick Lee 2022-10-12 14:35:09 -07:00 committed by GitHub
parent aa2fedaf78
commit e8242ace80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 590 additions and 38 deletions

View File

@ -79,8 +79,8 @@ func (k *Keeper) Deposit(
isNew := vaultShareRecord.Shares.AmountOf(amount.Denom).IsZero() isNew := vaultShareRecord.Shares.AmountOf(amount.Denom).IsZero()
if !isNew { if !isNew {
// If deposits for this vault already exists // If deposits for this vault already exists, call hook with user's existing shares
k.BeforeVaultDepositModified(ctx, amount.Denom, depositor, vaultRecord.TotalShares.Amount) k.BeforeVaultDepositModified(ctx, amount.Denom, depositor, vaultShareRecord.Shares.AmountOf(amount.Denom))
} }
// Increment VaultRecord total shares and account shares // Increment VaultRecord total shares and account shares

View File

@ -27,13 +27,16 @@ func TestHookTestSuite(t *testing.T) {
func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() { func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
suite.Keeper.ClearHooks() suite.Keeper.ClearHooks()
earnHooks := &mocks.EarnHooks{} earnHooks := mocks.NewEarnHooks(suite.T())
suite.Keeper.SetHooks(earnHooks) suite.Keeper.SetHooks(earnHooks)
vault1Denom := "usdx" vault1Denom := "usdx"
vault2Denom := "ukava" vault2Denom := "ukava"
deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100) acc1deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100)
deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100) acc1deposit2Amount := sdk.NewInt64Coin(vault2Denom, 200)
acc2deposit1Amount := sdk.NewInt64Coin(vault1Denom, 200)
acc2deposit2Amount := sdk.NewInt64Coin(vault2Denom, 300)
suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil) suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil) suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
@ -43,19 +46,24 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
sdk.NewInt64Coin(vault2Denom, 1000), sdk.NewInt64Coin(vault2Denom, 1000),
), 0) ), 0)
acc2 := suite.CreateAccount(sdk.NewCoins(
sdk.NewInt64Coin(vault1Denom, 1000),
sdk.NewInt64Coin(vault2Denom, 1000),
), 1)
// first deposit creates vault - calls AfterVaultDepositCreated with initial shares // first deposit creates vault - calls AfterVaultDepositCreated with initial shares
// shares are 1:1 // shares are 1:1
earnHooks.On( earnHooks.On(
"AfterVaultDepositCreated", "AfterVaultDepositCreated",
suite.Ctx, suite.Ctx,
deposit1Amount.Denom, acc1deposit1Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
deposit1Amount.Amount.ToDec(), acc1deposit1Amount.Amount.ToDec(),
).Once() ).Once()
err := suite.Keeper.Deposit( err := suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit1Amount, acc1deposit1Amount,
types.STRATEGY_TYPE_HARD, types.STRATEGY_TYPE_HARD,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -65,14 +73,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit1Amount.Denom, acc1deposit1Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
deposit1Amount.Amount.ToDec(), acc1deposit1Amount.Amount.ToDec(),
).Once() ).Once()
err = suite.Keeper.Deposit( err = suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit1Amount, acc1deposit1Amount,
types.STRATEGY_TYPE_HARD, types.STRATEGY_TYPE_HARD,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -89,14 +97,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit1Amount.Denom, acc1deposit1Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit1Amount.Denom), shareRecord.AmountOf(acc1deposit1Amount.Denom),
).Once() ).Once()
err = suite.Keeper.Deposit( err = suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit1Amount, acc1deposit1Amount,
types.STRATEGY_TYPE_HARD, types.STRATEGY_TYPE_HARD,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -105,14 +113,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"AfterVaultDepositCreated", "AfterVaultDepositCreated",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount.Amount.ToDec(), acc1deposit2Amount.Amount.ToDec(),
).Once() ).Once()
err = suite.Keeper.Deposit( err = suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -121,14 +129,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount.Amount.ToDec(), acc1deposit2Amount.Amount.ToDec(),
).Once() ).Once()
err = suite.Keeper.Deposit( err = suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -144,14 +152,131 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom), shareRecord.AmountOf(acc1deposit2Amount.Denom),
).Once() ).Once()
err = suite.Keeper.Deposit( err = suite.Keeper.Deposit(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// ------------------------------------------------------------
// Second account deposits
// first deposit by user - calls AfterVaultDepositCreated with user's shares
// not total shares
earnHooks.On(
"AfterVaultDepositCreated",
suite.Ctx,
acc2deposit1Amount.Denom,
acc2.GetAddress(),
acc2deposit1Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// second deposit adds to vault - calls BeforeVaultDepositModified
// shares given are the initial shares, not new the shares added to the vault
// and not the total vault shares
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit1Amount.Denom,
acc2.GetAddress(),
acc2deposit1Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// get the shares from the store from the last deposit
shareRecord2, found := suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// third deposit adds to vault - calls BeforeVaultDepositModified
// shares given are the shares added in previous deposit, not the shares added to the vault now
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit1Amount.Denom,
acc2.GetAddress(),
shareRecord2.AmountOf(acc2deposit1Amount.Denom),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// new deposit denom into vault creates the deposit and calls AfterVaultDepositCreated
earnHooks.On(
"AfterVaultDepositCreated",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
acc2deposit2Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// second deposit into vault calls BeforeVaultDepositModified with initial shares given
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
acc2deposit2Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// get the shares from the store from the last deposit
shareRecord2, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// third deposit into vault calls BeforeVaultDepositModified with shares from last deposit
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
shareRecord2.AmountOf(acc2deposit2Amount.Denom),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -168,15 +293,15 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit1Amount.Denom, acc1deposit1Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit1Amount.Denom), shareRecord.AmountOf(acc1deposit1Amount.Denom),
).Once() ).Once()
_, err = suite.Keeper.Withdraw( _, err = suite.Keeper.Withdraw(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
// 3 deposits, multiply original deposit amount by 3 // 3 deposits, multiply original deposit amount by 3
sdk.NewCoin(deposit1Amount.Denom, deposit1Amount.Amount.MulRaw(3)), sdk.NewCoin(acc1deposit1Amount.Denom, acc1deposit1Amount.Amount.MulRaw(3)),
types.STRATEGY_TYPE_HARD, types.STRATEGY_TYPE_HARD,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -192,14 +317,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom), shareRecord.AmountOf(acc1deposit2Amount.Denom),
).Once() ).Once()
_, err = suite.Keeper.Withdraw( _, err = suite.Keeper.Withdraw(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -215,14 +340,14 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom), shareRecord.AmountOf(acc1deposit2Amount.Denom),
).Once() ).Once()
_, err = suite.Keeper.Withdraw( _, err = suite.Keeper.Withdraw(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
@ -238,19 +363,111 @@ func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
earnHooks.On( earnHooks.On(
"BeforeVaultDepositModified", "BeforeVaultDepositModified",
suite.Ctx, suite.Ctx,
deposit2Amount.Denom, acc1deposit2Amount.Denom,
acc.GetAddress(), acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom), shareRecord.AmountOf(acc1deposit2Amount.Denom),
).Once() ).Once()
_, err = suite.Keeper.Withdraw( _, err = suite.Keeper.Withdraw(
suite.Ctx, suite.Ctx,
acc.GetAddress(), acc.GetAddress(),
deposit2Amount, acc1deposit2Amount,
types.STRATEGY_TYPE_SAVINGS, types.STRATEGY_TYPE_SAVINGS,
) )
suite.Require().NoError(err) suite.Require().NoError(err)
earnHooks.AssertExpectations(suite.T()) // ------------------------------------------------------------
// withdraw from acc2
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// all shares given to BeforeVaultDepositModified
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit1Amount.Denom,
acc2.GetAddress(),
shareRecord.AmountOf(acc2deposit1Amount.Denom),
).Once()
_, err = suite.Keeper.Withdraw(
suite.Ctx,
acc2.GetAddress(),
// 3 deposits, multiply original deposit amount by 3
sdk.NewCoin(acc2deposit1Amount.Denom, acc2deposit1Amount.Amount.MulRaw(3)),
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// test hooks on partial withdraw
shareRecord2, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
shareRecord2.AmountOf(acc2deposit2Amount.Denom),
).Once()
_, err = suite.Keeper.Withdraw(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// test hooks on second partial withdraw
shareRecord2, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
shareRecord2.AmountOf(acc2deposit2Amount.Denom),
).Once()
_, err = suite.Keeper.Withdraw(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// test hooks withdraw all remaining shares
shareRecord2, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc2.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
acc2deposit2Amount.Denom,
acc2.GetAddress(),
shareRecord2.AmountOf(acc2deposit2Amount.Denom),
).Once()
_, err = suite.Keeper.Withdraw(
suite.Ctx,
acc2.GetAddress(),
acc2deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
} }
func (suite *hookTestSuite) TestHooks_NoPanicsOnNilHooks() { func (suite *hookTestSuite) TestHooks_NoPanicsOnNilHooks() {

View File

@ -126,8 +126,8 @@ func (k *Keeper) Withdraw(
withdrawShares = vaultShareRecord.Shares.GetShare(withdrawAmount.Denom) withdrawShares = vaultShareRecord.Shares.GetShare(withdrawAmount.Denom)
} }
// Call hook before record is modified // Call hook before record is modified with the user's current shares
k.BeforeVaultDepositModified(ctx, wantAmount.Denom, from, vaultRecord.TotalShares.Amount) k.BeforeVaultDepositModified(ctx, wantAmount.Denom, from, accCurrentShares)
// Decrement VaultRecord and VaultShareRecord supplies - must delete same // Decrement VaultRecord and VaultShareRecord supplies - must delete same
// amounts // amounts

View File

@ -0,0 +1,197 @@
package keeper_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/incentive/testutil"
"github.com/kava-labs/kava/x/incentive/types"
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
)
func (suite *HandlerTestSuite) TestEarnLiquidClaim() {
userAddr1, userAddr2, validatorAddr1, validatorAddr2 := suite.addrs[0], suite.addrs[1], suite.addrs[2], suite.addrs[3]
valAddr1 := sdk.ValAddress(validatorAddr1)
valAddr2 := sdk.ValAddress(validatorAddr2)
authBuilder := suite.authBuilder().
WithSimpleAccount(userAddr1, cs(c("ukava", 1e12))).
WithSimpleAccount(userAddr2, cs(c("ukava", 1e12))).
WithSimpleAccount(validatorAddr1, cs(c("ukava", 1e12))).
WithSimpleAccount(validatorAddr2, cs(c("ukava", 1e12)))
incentBuilder := suite.incentiveBuilder()
savingsBuilder := testutil.NewSavingsGenesisBuilder().
WithSupportedDenoms("bkava")
earnBuilder := suite.earnBuilder().
WithVault(earntypes.AllowedVault{
Denom: "bkava",
Strategies: earntypes.StrategyTypes{earntypes.STRATEGY_TYPE_SAVINGS},
IsPrivateVault: false,
AllowedDepositors: nil,
})
suite.SetupWithGenState(authBuilder, incentBuilder, earnBuilder, savingsBuilder)
// ak := suite.App.GetAccountKeeper()
// bk := suite.App.GetBankKeeper()
sk := suite.App.GetStakingKeeper()
lq := suite.App.GetLiquidKeeper()
mk := suite.App.GetMintKeeper()
dk := suite.App.GetDistrKeeper()
ik := suite.App.GetIncentiveKeeper()
// Use ukava for mint denom
mParams := mk.GetParams(suite.Ctx)
mParams.MintDenom = "ukava"
mk.SetParams(suite.Ctx, mParams)
bkavaDenom1 := lq.GetLiquidStakingTokenDenom(valAddr1)
bkavaDenom2 := lq.GetLiquidStakingTokenDenom(valAddr2)
err := suite.App.FundModuleAccount(suite.Ctx, distrtypes.ModuleName, cs(c("ukava", 1e12)))
suite.NoError(err)
// Create validators
err = suite.DeliverMsgCreateValidator(valAddr1, c("ukava", 1e9))
suite.Require().NoError(err)
err = suite.DeliverMsgCreateValidator(valAddr2, c("ukava", 1e9))
suite.Require().NoError(err)
// new block required to bond validator
suite.NextBlockAfter(7 * time.Second)
// Now the delegation is bonded, accumulate some delegator rewards
suite.NextBlockAfter(7 * time.Second)
// Create delegations from users
// User 1: 1e9 ukava to validator 1
// User 2: 99e9 ukava to validator 1 AND 2
err = suite.DeliverMsgDelegate(userAddr1, valAddr1, c("ukava", 1e9))
suite.Require().NoError(err)
err = suite.DeliverMsgDelegate(userAddr2, valAddr1, c("ukava", 99e9))
suite.Require().NoError(err)
err = suite.DeliverMsgDelegate(userAddr2, valAddr2, c("ukava", 99e9))
suite.Require().NoError(err)
// Mint liquid tokens
err = suite.DeliverMsgMintDerivative(userAddr1, valAddr1, c("ukava", 1e9))
suite.Require().NoError(err)
err = suite.DeliverMsgMintDerivative(userAddr2, valAddr1, c("ukava", 99e9))
suite.Require().NoError(err)
err = suite.DeliverMsgMintDerivative(userAddr2, valAddr2, c("ukava", 99e9))
suite.Require().NoError(err)
// Deposit liquid tokens to earn
err = suite.DeliverEarnMsgDeposit(userAddr1, c(bkavaDenom1, 1e9), earntypes.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
err = suite.DeliverEarnMsgDeposit(userAddr2, c(bkavaDenom1, 99e9), earntypes.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
err = suite.DeliverEarnMsgDeposit(userAddr2, c(bkavaDenom2, 99e9), earntypes.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// BeginBlocker to update minter annual provisions as it starts at 0 which results in no minted coins
_ = suite.App.BeginBlocker(suite.Ctx, abci.RequestBeginBlock{})
// DeliverMsgCreateValidator uses a generated pubkey, so we need to fetch
// the validator to get the correct pubkey
validator1, found := sk.GetValidator(suite.Ctx, valAddr1)
suite.Require().True(found)
pk, err := validator1.ConsPubKey()
suite.Require().NoError(err)
val := abci.Validator{
Address: pk.Address(),
Power: 100,
}
suite.Ctx = suite.Ctx.
WithBlockHeight(suite.Ctx.BlockHeight() + 1).
WithBlockTime(suite.Ctx.BlockTime().Add(1 * time.Hour))
// Accumulate some staking rewards
_ = suite.App.BeginBlocker(suite.Ctx, abci.RequestBeginBlock{
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: true,
}},
},
})
liquidMacc := suite.App.GetAccountKeeper().GetModuleAccount(suite.Ctx, liquidtypes.ModuleAccountName)
delegation, found := sk.GetDelegation(suite.Ctx, liquidMacc.GetAddress(), valAddr1)
suite.Require().True(found)
// Get amount of rewards
endingPeriod := dk.IncrementValidatorPeriod(suite.Ctx, validator1)
delegationRewards := dk.CalculateDelegationRewards(suite.Ctx, validator1, delegation, endingPeriod)
// Accumulate rewards - claim rewards
rewardPeriod := types.NewMultiRewardPeriod(
true,
"bkava", // reward period is set for "bkava" to apply to all vaults
time.Unix(0, 0), // ensure the test is within start and end times
distantFuture,
cs(), // no incentives, so only the staking rewards are distributed
)
err = ik.AccumulateEarnRewards(suite.Ctx, rewardPeriod)
suite.Require().NoError(err)
preClaimBal1 := suite.GetBalance(userAddr1)
preClaimBal2 := suite.GetBalance(userAddr2)
// Claim ukava staking rewards
denomsToClaim := map[string]string{"ukava": "large"}
selections := types.NewSelectionsFromMap(denomsToClaim)
msg1 := types.NewMsgClaimEarnReward(userAddr1.String(), selections)
msg2 := types.NewMsgClaimEarnReward(userAddr2.String(), selections)
err = suite.DeliverIncentiveMsg(&msg1)
suite.Require().NoError(err)
err = suite.DeliverIncentiveMsg(&msg2)
suite.Require().NoError(err)
// Check rewards were paid out
// User 1 gets 1% of rewards
// User 2 gets 99% of rewards
stakingRewards1 := delegationRewards.
AmountOf("ukava").
QuoInt64(100).
RoundInt()
suite.BalanceEquals(userAddr1, preClaimBal1.Add(sdk.NewCoin("ukava", stakingRewards1)))
// Total * 99 / 100
stakingRewards2 := delegationRewards.
AmountOf("ukava").
MulInt64(99).
QuoInt64(100).
TruncateInt()
suite.BalanceEquals(userAddr2, preClaimBal2.Add(sdk.NewCoin("ukava", stakingRewards2)))
suite.Equal(delegationRewards.AmountOf("ukava").TruncateInt(), stakingRewards1.Add(stakingRewards2))
// Check that claimed coins have been removed from a claim's reward
suite.EarnRewardEquals(userAddr1, cs())
suite.EarnRewardEquals(userAddr2, cs())
}
// earnBuilder returns a new earn genesis builder with a genesis time and multipliers set
func (suite *HandlerTestSuite) earnBuilder() testutil.EarnGenesisBuilder {
return testutil.NewEarnGenesisBuilder().
WithGenesisTime(suite.genesisTime)
}

View File

@ -7,8 +7,10 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
earntypes "github.com/kava-labs/kava/x/earn/types"
hardtypes "github.com/kava-labs/kava/x/hard/types" hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
savingstypes "github.com/kava-labs/kava/x/savings/types"
) )
const ( const (
@ -168,6 +170,24 @@ func (builder IncentiveGenesisBuilder) WithSimpleUSDXRewardPeriod(ctype string,
)) ))
} }
// WithInitializedEarnRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
func (builder IncentiveGenesisBuilder) WithInitializedEarnRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
builder.Params.EarnRewardPeriods = append(builder.Params.EarnRewardPeriods, period)
accumulationTimeForPeriod := types.NewAccumulationTime(period.CollateralType, builder.genesisTime)
builder.EarnRewardState.AccumulationTimes = append(
builder.EarnRewardState.AccumulationTimes,
accumulationTimeForPeriod,
)
return builder
}
func (builder IncentiveGenesisBuilder) WithSimpleEarnRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedEarnRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
}
func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.MultipliersPerDenoms) IncentiveGenesisBuilder { func (builder IncentiveGenesisBuilder) WithMultipliers(multipliers types.MultipliersPerDenoms) IncentiveGenesisBuilder {
builder.Params.ClaimMultipliers = multipliers builder.Params.ClaimMultipliers = multipliers
@ -281,3 +301,75 @@ func (builder IncentiveGenesisBuilder) WithInitializedSavingsRewardPeriod(period
func (builder IncentiveGenesisBuilder) WithSimpleSavingsRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder { func (builder IncentiveGenesisBuilder) WithSimpleSavingsRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder {
return builder.WithInitializedSavingsRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond)) return builder.WithInitializedSavingsRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
} }
// EarnGenesisBuilder is a tool for creating a earn genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type EarnGenesisBuilder struct {
earntypes.GenesisState
genesisTime time.Time
}
func NewEarnGenesisBuilder() EarnGenesisBuilder {
return EarnGenesisBuilder{
GenesisState: earntypes.DefaultGenesisState(),
}
}
func (builder EarnGenesisBuilder) Build() earntypes.GenesisState {
return builder.GenesisState
}
func (builder EarnGenesisBuilder) BuildMarshalled(cdc codec.JSONCodec) app.GenesisState {
built := builder.Build()
return app.GenesisState{
earntypes.ModuleName: cdc.MustMarshalJSON(&built),
}
}
func (builder EarnGenesisBuilder) WithGenesisTime(genTime time.Time) EarnGenesisBuilder {
builder.genesisTime = genTime
return builder
}
func (builder EarnGenesisBuilder) WithVault(vault earntypes.AllowedVault) EarnGenesisBuilder {
builder.Params.AllowedVaults = append(builder.Params.AllowedVaults, vault)
return builder
}
// SavingsGenesisBuilder is a tool for creating a savings genesis state.
// Helper methods add values onto a default genesis state.
// All methods are immutable and return updated copies of the builder.
type SavingsGenesisBuilder struct {
savingstypes.GenesisState
genesisTime time.Time
}
func NewSavingsGenesisBuilder() SavingsGenesisBuilder {
return SavingsGenesisBuilder{
GenesisState: savingstypes.DefaultGenesisState(),
}
}
func (builder SavingsGenesisBuilder) Build() savingstypes.GenesisState {
return builder.GenesisState
}
func (builder SavingsGenesisBuilder) BuildMarshalled(cdc codec.JSONCodec) app.GenesisState {
built := builder.Build()
return app.GenesisState{
savingstypes.ModuleName: cdc.MustMarshalJSON(&built),
}
}
func (builder SavingsGenesisBuilder) WithGenesisTime(genTime time.Time) SavingsGenesisBuilder {
builder.genesisTime = genTime
return builder
}
func (builder SavingsGenesisBuilder) WithSupportedDenoms(denoms ...string) SavingsGenesisBuilder {
builder.Params.SupportedDenoms = append(builder.Params.SupportedDenoms, denoms...)
return builder
}

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
@ -21,10 +22,14 @@ import (
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
committeekeeper "github.com/kava-labs/kava/x/committee/keeper" committeekeeper "github.com/kava-labs/kava/x/committee/keeper"
committeetypes "github.com/kava-labs/kava/x/committee/types" committeetypes "github.com/kava-labs/kava/x/committee/types"
earnkeeper "github.com/kava-labs/kava/x/earn/keeper"
earntypes "github.com/kava-labs/kava/x/earn/types"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper" hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
hardtypes "github.com/kava-labs/kava/x/hard/types" hardtypes "github.com/kava-labs/kava/x/hard/types"
incentivekeeper "github.com/kava-labs/kava/x/incentive/keeper" incentivekeeper "github.com/kava-labs/kava/x/incentive/keeper"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper"
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
swapkeeper "github.com/kava-labs/kava/x/swap/keeper" swapkeeper "github.com/kava-labs/kava/x/swap/keeper"
swaptypes "github.com/kava-labs/kava/x/swap/types" swaptypes "github.com/kava-labs/kava/x/swap/types"
) )
@ -87,6 +92,8 @@ func (suite *IntegrationTester) DeliverIncentiveMsg(msg sdk.Msg) error {
_, err = msgServer.ClaimUSDXMintingReward(sdk.WrapSDKContext(suite.Ctx), msg) _, err = msgServer.ClaimUSDXMintingReward(sdk.WrapSDKContext(suite.Ctx), msg)
case *types.MsgClaimDelegatorReward: case *types.MsgClaimDelegatorReward:
_, err = msgServer.ClaimDelegatorReward(sdk.WrapSDKContext(suite.Ctx), msg) _, err = msgServer.ClaimDelegatorReward(sdk.WrapSDKContext(suite.Ctx), msg)
case *types.MsgClaimEarnReward:
_, err = msgServer.ClaimEarnReward(sdk.WrapSDKContext(suite.Ctx), msg)
default: default:
panic("unhandled incentive msg") panic("unhandled incentive msg")
} }
@ -194,6 +201,30 @@ func (suite *IntegrationTester) DeliverCDPMsgBorrow(owner sdk.AccAddress, collat
return err return err
} }
func (suite *IntegrationTester) DeliverMsgMintDerivative(
sender sdk.AccAddress,
validator sdk.ValAddress,
amount sdk.Coin,
) error {
msg := liquidtypes.NewMsgMintDerivative(sender, validator, amount)
msgServer := liquidkeeper.NewMsgServerImpl(suite.App.GetLiquidKeeper())
_, err := msgServer.MintDerivative(sdk.WrapSDKContext(suite.Ctx), &msg)
return err
}
func (suite *IntegrationTester) DeliverEarnMsgDeposit(
depositor sdk.AccAddress,
amount sdk.Coin,
strategy earntypes.StrategyType,
) error {
msg := earntypes.NewMsgDeposit(depositor.String(), amount, strategy)
msgServer := earnkeeper.NewMsgServerImpl(suite.App.GetEarnKeeper())
_, err := msgServer.Deposit(sdk.WrapSDKContext(suite.Ctx), msg)
return err
}
func (suite *IntegrationTester) ProposeAndVoteOnNewParams(voter sdk.AccAddress, committeeID uint64, changes []proposaltypes.ParamChange) { func (suite *IntegrationTester) ProposeAndVoteOnNewParams(voter sdk.AccAddress, committeeID uint64, changes []proposaltypes.ParamChange) {
propose, err := committeetypes.NewMsgSubmitProposal( propose, err := committeetypes.NewMsgSubmitProposal(
proposaltypes.NewParameterChangeProposal( proposaltypes.NewParameterChangeProposal(
@ -292,3 +323,18 @@ func (suite *IntegrationTester) USDXRewardEquals(owner sdk.AccAddress, expected
suite.Require().Truef(found, "expected delegator claim to be found for %s", owner) suite.Require().Truef(found, "expected delegator claim to be found for %s", owner)
suite.Equalf(expected, claim.Reward, "expected delegator claim reward to be %s, but got %s", expected, claim.Reward) suite.Equalf(expected, claim.Reward, "expected delegator claim reward to be %s, but got %s", expected, claim.Reward)
} }
func (suite *IntegrationTester) EarnRewardEquals(owner sdk.AccAddress, expected sdk.Coins) {
claim, found := suite.App.GetIncentiveKeeper().GetEarnClaim(suite.Ctx, owner)
suite.Require().Truef(found, "expected earn claim to be found for %s", owner)
suite.Truef(expected.IsEqual(claim.Reward), "expected earn claim reward to be %s, but got %s", expected, claim.Reward)
}
// AddTestAddrsFromPubKeys adds the addresses into the SimApp providing only the public keys.
func (suite *IntegrationTester) AddTestAddrsFromPubKeys(ctx sdk.Context, pubKeys []cryptotypes.PubKey, accAmt sdk.Int) {
initCoins := sdk.NewCoins(sdk.NewCoin(suite.App.GetStakingKeeper().BondDenom(ctx), accAmt))
for _, pk := range pubKeys {
suite.App.FundAccount(ctx, sdk.AccAddress(pk.Address()), initCoins)
}
}