0g-chain/x/incentive/keeper/rewards_supply_sync_test.go
Denali Marsh eaeaf20e83
Incentive savings hooks + init/sync of savings claims (#1209)
* update savings module macc balances getter

* add savings keeper to incentive module

* add savings keeper to incentive module #2

* savings reward syncing

* claim savings reward

* update txs, queries

* update txs, queries #2

* update claim test

* add savings keeper to incentive module in app.go

* re-commit files to disk

* define and call hooks

* keeper methods for init/sync savings reward

* update other tests for easier extendibility

* init savings reward test

* add helper methods to global incentive unit tester

* sync savings test progress

* savings init fix + completed tests

* sync savings updates + tests

* nit: simplify false check

* fix: calculate set difference of incoming deposit denoms

Co-authored-by: karzak <kjydavis3@gmail.com>
2022-04-21 16:19:03 +02:00

338 lines
11 KiB
Go

package keeper_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"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
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.storeHardClaim(claim)
globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
Build()
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.storeHardClaim(claim)
suite.storeGlobalSupplyIndexes(unchangingIndexes)
deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...).
Build()
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.storeHardClaim(claim)
globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build()
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.storeHardClaim(claim)
globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build()
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.storeHardClaim(claim)
suite.storeGlobalSupplyIndexes(types.MultiRewardIndexes{
{
CollateralType: "depositdenom",
RewardIndexes: types.RewardIndexes{
{
CollateralType: "rewarddenom",
RewardFactor: d("2000.002"),
},
},
},
})
deposit := NewHardDepositBuilder(claim.Owner).
WithSourceShares("depositdenom", 1e9).
Build()
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.storeHardClaim(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 := NewHardDepositBuilder(claim.Owner).
WithSourceShares("rewarded", 1e9).
WithSourceShares("newlyrewarded", 1e9).
Build()
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.storeHardClaim(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 := NewHardDepositBuilder(claim.Owner).
WithSourceShares("deposited", 1e9).
Build()
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,
)
}
// HardDepositBuilder is a tool for creating a hard deposit in tests.
// The builder inherits from hard.Deposit, so fields can be accessed directly if a helper method doesn't exist.
type HardDepositBuilder struct {
hardtypes.Deposit
}
// NewHardDepositBuilder creates a HardDepositBuilder containing an empty deposit.
func NewHardDepositBuilder(depositor sdk.AccAddress) HardDepositBuilder {
return HardDepositBuilder{
Deposit: hardtypes.Deposit{
Depositor: depositor,
}}
}
// Build assembles and returns the final deposit.
func (builder HardDepositBuilder) Build() hardtypes.Deposit { return builder.Deposit }
// WithSourceShares adds a deposit amount and factor such that the source shares for this deposit is equal to specified.
// With a factor of 1, the deposit amount is the source shares. This picks an arbitrary factor to ensure factors are accounted for in production code.
func (builder HardDepositBuilder) WithSourceShares(denom string, shares int64) HardDepositBuilder {
if !builder.Amount.AmountOf(denom).Equal(sdk.ZeroInt()) {
panic("adding to amount with existing denom not implemented")
}
if _, f := builder.Index.GetInterestFactor(denom); f {
panic("adding to indexes with existing denom not implemented")
}
// pick arbitrary factor
factor := sdk.MustNewDecFromStr("2")
// Calculate deposit amount that would equal the requested source shares given the above factor.
amt := sdk.NewInt(shares).Mul(factor.RoundInt())
builder.Amount = builder.Amount.Add(sdk.NewCoin(denom, amt))
builder.Index = builder.Index.SetInterestFactor(denom, factor)
return builder
}
// WithArbitrarySourceShares adds arbitrary deposit amounts and indexes for each specified denom.
func (builder HardDepositBuilder) WithArbitrarySourceShares(denoms ...string) HardDepositBuilder {
const arbitraryShares = 1e9
for _, denom := range denoms {
builder = builder.WithSourceShares(denom, arbitraryShares)
}
return builder
}