mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
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>
This commit is contained in:
parent
c2e53f2d00
commit
eaeaf20e83
@ -168,10 +168,10 @@ func (h Hooks) BeforePoolDepositModified(ctx sdk.Context, poolID string, deposit
|
||||
|
||||
// AfterSavingsDepositCreated function that runs after a deposit is created
|
||||
func (h Hooks) AfterSavingsDepositCreated(ctx sdk.Context, deposit savingstypes.Deposit) {
|
||||
// TODO: InitializeSavingsReward
|
||||
h.k.InitializeSavingsReward(ctx, deposit)
|
||||
}
|
||||
|
||||
// BeforeSavingsDepositModified function that runs before a deposit is modified
|
||||
func (h Hooks) BeforeSavingsDepositModified(ctx sdk.Context, deposit savingstypes.Deposit, incomingDenoms []string) {
|
||||
// TODO: SynchronizeSavingsReward
|
||||
h.k.SynchronizeSavingsReward(ctx, deposit, incomingDenoms)
|
||||
}
|
||||
|
@ -38,30 +38,54 @@ func (k Keeper) AccumulateSavingsRewards(ctx sdk.Context, rewardPeriod types.Mul
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) SynchronizeSavingsClaim(ctx sdk.Context, owner sdk.AccAddress) {
|
||||
deposit, found := k.savingsKeeper.GetDeposit(ctx, owner)
|
||||
// InitializeSavingsReward initializes a savings claim by creating the claim and
|
||||
// setting the reward factor indexes
|
||||
func (k Keeper) InitializeSavingsReward(ctx sdk.Context, deposit savingstypes.Deposit) {
|
||||
claim, found := k.GetSavingsClaim(ctx, deposit.Depositor)
|
||||
if !found {
|
||||
return
|
||||
claim = types.NewSavingsClaim(deposit.Depositor, sdk.Coins{}, nil)
|
||||
}
|
||||
k.SynchronizeSavingsReward(ctx, deposit)
|
||||
|
||||
rewardIndexes := claim.RewardIndexes
|
||||
for _, coin := range deposit.Amount {
|
||||
globalRewardIndexes, found := k.GetSavingsRewardIndexes(ctx, coin.Denom)
|
||||
if !found {
|
||||
globalRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
rewardIndexes = rewardIndexes.With(coin.Denom, globalRewardIndexes)
|
||||
}
|
||||
claim.RewardIndexes = rewardIndexes
|
||||
|
||||
k.SetSavingsClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// SynchronizeSavingsReward updates the claim object by adding any accumulated rewards
|
||||
// and updating the reward index value
|
||||
func (k Keeper) SynchronizeSavingsReward(ctx sdk.Context, deposit savingstypes.Deposit) {
|
||||
func (k Keeper) SynchronizeSavingsReward(ctx sdk.Context, deposit savingstypes.Deposit, incomingDenoms []string) {
|
||||
claim, found := k.GetSavingsClaim(ctx, deposit.Depositor)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
// Source shares for savings deposits is the deposit amount
|
||||
for _, coin := range deposit.Amount {
|
||||
claim = k.synchronizeSingleSavingsReward(ctx, claim, coin.Denom, coin.Amount.ToDec())
|
||||
// Set the reward factor on claim to the global reward factor for each incoming denom
|
||||
for _, denom := range incomingDenoms {
|
||||
globalRewardIndexes, found := k.GetSavingsRewardIndexes(ctx, denom)
|
||||
if !found {
|
||||
globalRewardIndexes = types.RewardIndexes{}
|
||||
}
|
||||
claim.RewardIndexes = claim.RewardIndexes.With(denom, globalRewardIndexes)
|
||||
}
|
||||
|
||||
// Existing denoms have their reward indexes + reward amount synced
|
||||
existingDenoms := setDifference(getDenoms(deposit.Amount), incomingDenoms)
|
||||
for _, denom := range existingDenoms {
|
||||
claim = k.synchronizeSingleSavingsReward(ctx, claim, denom, deposit.Amount.AmountOf(denom).ToDec())
|
||||
}
|
||||
|
||||
k.SetSavingsClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// synchronizeSingleSavingsReward synchronizes a single rewarded savings denom in a s claim.
|
||||
// synchronizeSingleSavingsReward synchronizes a single rewarded savings denom in a savings claim.
|
||||
// It returns the claim without setting in the store.
|
||||
// The public methods for accessing and modifying claims are preferred over this one. Direct modification of claims is easy to get wrong.
|
||||
func (k Keeper) synchronizeSingleSavingsReward(ctx sdk.Context, claim types.SavingsClaim, denom string, sourceShares sdk.Dec) types.SavingsClaim {
|
||||
@ -115,3 +139,13 @@ func (k Keeper) GetSynchronizedSavingsClaim(ctx sdk.Context, owner sdk.AccAddres
|
||||
|
||||
return claim, true
|
||||
}
|
||||
|
||||
// SynchronizeSavingsClaim syncs a savings reward claim from its store
|
||||
func (k Keeper) SynchronizeSavingsClaim(ctx sdk.Context, owner sdk.AccAddress) {
|
||||
deposit, found := k.savingsKeeper.GetDeposit(ctx, owner)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
k.SynchronizeSavingsReward(ctx, deposit, []string{})
|
||||
}
|
||||
|
194
x/incentive/keeper/rewards_savings_init_test.go
Normal file
194
x/incentive/keeper/rewards_savings_init_test.go
Normal file
@ -0,0 +1,194 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
savingstypes "github.com/kava-labs/kava/x/savings/types"
|
||||
)
|
||||
|
||||
// InitializeSavingsRewardTests runs unit tests for the keeper.InitializeSavingsReward method
|
||||
type InitializeSavingsRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestInitializeSavingsRewardTests(t *testing.T) {
|
||||
suite.Run(t, new(InitializeSavingsRewardTests))
|
||||
}
|
||||
|
||||
func (suite *InitializeSavingsRewardTests) TestClaimAddedWhenClaimDoesNotExistAndNoRewards() {
|
||||
// When a claim doesn't exist, and a user deposits to a non-rewarded pool;
|
||||
// then a claim is added with no rewards and no indexes
|
||||
|
||||
// no global indexes stored as this pool is not rewarded
|
||||
|
||||
owner := arbitraryAddress()
|
||||
|
||||
amount := sdk.NewCoin("test", sdk.OneInt())
|
||||
deposit := savingstypes.NewDeposit(owner, sdk.NewCoins(amount))
|
||||
|
||||
suite.keeper.InitializeSavingsReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, owner)
|
||||
suite.True(found)
|
||||
// A new claim should have empty indexes. It doesn't strictly need the poolID either.
|
||||
expectedIndexes := types.MultiRewardIndexes{{
|
||||
CollateralType: amount.Denom,
|
||||
RewardIndexes: nil,
|
||||
}}
|
||||
suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
|
||||
// a new claim should start with 0 rewards
|
||||
suite.Equal(sdk.Coins(nil), syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *InitializeSavingsRewardTests) TestClaimAddedWhenClaimDoesNotExistAndRewardsExist() {
|
||||
// When a claim doesn't exist, and a user deposits to a rewarded pool;
|
||||
// then a claim is added with no rewards and indexes matching the global indexes
|
||||
|
||||
amount := sdk.NewCoin("test", sdk.OneInt())
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: amount.Denom,
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalSavingsIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
|
||||
deposit := savingstypes.NewDeposit(owner, sdk.NewCoins(amount))
|
||||
suite.keeper.InitializeSavingsReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, owner)
|
||||
suite.True(found)
|
||||
// a new claim should start with the current global indexes
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
// a new claim should start with 0 rewards
|
||||
suite.Equal(sdk.Coins(nil), syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *InitializeSavingsRewardTests) TestClaimUpdatedWhenClaimExistsAndNoRewards() {
|
||||
// When a claim exists, and a user deposits to a new non-rewarded denom;
|
||||
// then the claim's rewards don't change
|
||||
|
||||
preexistingDenom := "preexisting"
|
||||
preexistingIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
}
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
RewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: preexistingDenom,
|
||||
RewardIndexes: preexistingIndexes,
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
// no global indexes stored as the new denom is not rewarded
|
||||
newDenom := "test"
|
||||
deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(newDenom, sdk.OneInt())))
|
||||
suite.keeper.InitializeSavingsReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
suite.True(found)
|
||||
|
||||
// The preexisting indexes shouldn't be changed. It doesn't strictly need the new denom either.
|
||||
expectedIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: preexistingDenom,
|
||||
RewardIndexes: preexistingIndexes,
|
||||
},
|
||||
{
|
||||
CollateralType: newDenom,
|
||||
RewardIndexes: nil,
|
||||
},
|
||||
}
|
||||
suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
|
||||
// init should never alter the rewards
|
||||
suite.Equal(claim.Reward, syncedClaim.Reward)
|
||||
}
|
||||
|
||||
func (suite *InitializeSavingsRewardTests) TestClaimUpdatedWhenClaimExistsAndRewardsExist() {
|
||||
// When a claim exists, and a user deposits to a new rewarded denom;
|
||||
// then the claim's rewards don't change and the indexes are updated to match the global indexes
|
||||
|
||||
preexistingDenom := "preexisting"
|
||||
preexistingIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
}
|
||||
|
||||
newDenom := "test"
|
||||
newIndexes := types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "otherrewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
}
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
RewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: preexistingDenom,
|
||||
RewardIndexes: preexistingIndexes,
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: preexistingDenom,
|
||||
RewardIndexes: increaseRewardFactors(preexistingIndexes),
|
||||
},
|
||||
{
|
||||
CollateralType: newDenom,
|
||||
RewardIndexes: newIndexes,
|
||||
},
|
||||
}
|
||||
suite.storeGlobalSavingsIndexes(globalIndexes)
|
||||
|
||||
deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(newDenom, sdk.OneInt())))
|
||||
suite.keeper.InitializeSavingsReward(suite.ctx, deposit)
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
// only the indexes for the new denom should be updated
|
||||
expectedIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: preexistingDenom,
|
||||
RewardIndexes: preexistingIndexes,
|
||||
},
|
||||
{
|
||||
CollateralType: newDenom,
|
||||
RewardIndexes: newIndexes,
|
||||
},
|
||||
}
|
||||
suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
|
||||
// init should never alter the rewards
|
||||
suite.Equal(claim.Reward, syncedClaim.Reward)
|
||||
}
|
245
x/incentive/keeper/rewards_savings_sync_test.go
Normal file
245
x/incentive/keeper/rewards_savings_sync_test.go
Normal file
@ -0,0 +1,245 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
savingstypes "github.com/kava-labs/kava/x/savings/types"
|
||||
)
|
||||
|
||||
// SynchronizeSavingsRewardTests runs unit tests for the keeper.SynchronizeSavingsReward method
|
||||
type SynchronizeSavingsRewardTests struct {
|
||||
unitTester
|
||||
}
|
||||
|
||||
func TestSynchronizeSavingsReward(t *testing.T) {
|
||||
suite.Run(t, new(SynchronizeSavingsRewardTests))
|
||||
}
|
||||
|
||||
func (suite *SynchronizeSavingsRewardTests) TestClaimUpdatedWhenGlobalIndexesHaveIncreased() {
|
||||
// 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, and the claim indexes are updated
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
denom := "test"
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
RewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: denom,
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: denom,
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeGlobalSavingsIndexes(globalIndexes)
|
||||
|
||||
userShares := i(1e9)
|
||||
deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(denom, userShares)))
|
||||
suite.keeper.SynchronizeSavingsReward(suite.ctx, deposit, []string{})
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
// indexes updated from global
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
// new reward is (new index - old index) * user shares
|
||||
suite.Equal(
|
||||
cs(c("rewarddenom", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeSavingsRewardTests) TestClaimUnchangedWhenGlobalIndexesUnchanged() {
|
||||
denom := "test"
|
||||
unchangingIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: denom,
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "rewarddenom",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: arbitraryCoins(),
|
||||
},
|
||||
RewardIndexes: unchangingIndexes,
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
suite.storeGlobalSavingsIndexes(unchangingIndexes)
|
||||
|
||||
userShares := i(1e9)
|
||||
deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(denom, userShares)))
|
||||
suite.keeper.SynchronizeSavingsReward(suite.ctx, deposit, []string{})
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
// claim should have the same rewards and indexes as before
|
||||
suite.Equal(claim, syncedClaim)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeSavingsRewardTests) TestClaimUpdatedWhenNewRewardAdded() {
|
||||
originalReward := arbitraryCoins()
|
||||
newlyRewardedDenom := "newlyRewardedDenom"
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
RewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "currentlyRewardedDenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: "currentlyRewardedDenom",
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("2000.002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CollateralType: newlyRewardedDenom,
|
||||
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.storeGlobalSavingsIndexes(globalIndexes)
|
||||
|
||||
userShares := i(1e9)
|
||||
deposit := savingstypes.NewDeposit(claim.Owner,
|
||||
sdk.NewCoins(
|
||||
sdk.NewCoin("currentlyRewardedDenom", userShares),
|
||||
sdk.NewCoin(newlyRewardedDenom, userShares),
|
||||
),
|
||||
)
|
||||
|
||||
suite.keeper.SynchronizeSavingsReward(suite.ctx, deposit, []string{newlyRewardedDenom})
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
// the new indexes should be added to the claim and the old ones should be updated
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
// new reward is (new index - old index) * shares for the synced deposit
|
||||
// The old index for `newlyrewarded` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
suite.Equal(
|
||||
cs(c("reward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *SynchronizeSavingsRewardTests) TestClaimUpdatedWhenNewRewardDenomAdded() {
|
||||
// When a new reward coin is added (via gov) to an already rewarded denom (that the user has already deposited to), and the claim is synced;
|
||||
// Then the user earns rewards for the time since the reward was added, and the new indexes are added.
|
||||
|
||||
originalReward := arbitraryCoins()
|
||||
denom := "base"
|
||||
|
||||
claim := types.SavingsClaim{
|
||||
BaseMultiClaim: types.BaseMultiClaim{
|
||||
Owner: arbitraryAddress(),
|
||||
Reward: originalReward,
|
||||
},
|
||||
RewardIndexes: types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: denom,
|
||||
RewardIndexes: types.RewardIndexes{
|
||||
{
|
||||
CollateralType: "reward",
|
||||
RewardFactor: d("1000.001"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.storeSavingsClaim(claim)
|
||||
|
||||
globalIndexes := types.MultiRewardIndexes{
|
||||
{
|
||||
CollateralType: denom,
|
||||
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.storeGlobalSavingsIndexes(globalIndexes)
|
||||
|
||||
userShares := i(1e9)
|
||||
deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(denom, userShares)))
|
||||
suite.keeper.SynchronizeSavingsReward(suite.ctx, deposit, []string{})
|
||||
|
||||
syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
|
||||
// indexes should have the new reward denom added
|
||||
suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
|
||||
// new reward is (new index - old index) * shares
|
||||
// The old index for `otherreward` isn't in the claim, so it's added starting at 0 for calculating the reward.
|
||||
suite.Equal(
|
||||
cs(c("reward", 1_000_001_000_000), c("otherreward", 1_000_001_000_000)).Add(originalReward...),
|
||||
syncedClaim.Reward,
|
||||
)
|
||||
}
|
||||
|
||||
func getDenoms(coins sdk.Coins) []string {
|
||||
denoms := []string{}
|
||||
for _, coin := range coins {
|
||||
denoms = append(denoms, coin.Denom)
|
||||
}
|
||||
return denoms
|
||||
}
|
@ -32,7 +32,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimExi
|
||||
globalIndexes := nonEmptyMultiRewardIndexes
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -46,7 +46,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimDoe
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
owner := arbitraryAddress()
|
||||
deposit := NewDepositBuilder(owner).
|
||||
deposit := NewHardDepositBuilder(owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -66,7 +66,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetEmptyForMiss
|
||||
// This happens when a deposit denom has no rewards associated with it.
|
||||
expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
|
||||
depositedDenoms := extractCollateralTypes(expectedIndexes)
|
||||
deposit := NewDepositBuilder(owner).
|
||||
deposit := NewHardDepositBuilder(owner).
|
||||
WithArbitrarySourceShares(depositedDenoms...).
|
||||
Build()
|
||||
|
||||
|
@ -32,7 +32,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenGlo
|
||||
|
||||
globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -56,7 +56,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUnchangedWhenG
|
||||
|
||||
suite.storeGlobalSupplyIndexes(unchangingIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -80,7 +80,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
|
||||
globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -105,7 +105,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
|
||||
globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -154,7 +154,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenGlobal
|
||||
},
|
||||
})
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithSourceShares("depositdenom", 1e9).
|
||||
Build()
|
||||
|
||||
@ -216,7 +216,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
|
||||
}
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithSourceShares("rewarded", 1e9).
|
||||
WithSourceShares("newlyrewarded", 1e9).
|
||||
Build()
|
||||
@ -274,7 +274,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
|
||||
}
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithSourceShares("deposited", 1e9).
|
||||
Build()
|
||||
|
||||
@ -289,26 +289,26 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
|
||||
)
|
||||
}
|
||||
|
||||
// DepositBuilder is a tool for creating a hard deposit in tests.
|
||||
// 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 DepositBuilder struct {
|
||||
type HardDepositBuilder struct {
|
||||
hardtypes.Deposit
|
||||
}
|
||||
|
||||
// NewDepositBuilder creates a DepositBuilder containing an empty deposit.
|
||||
func NewDepositBuilder(depositor sdk.AccAddress) DepositBuilder {
|
||||
return DepositBuilder{
|
||||
// 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 DepositBuilder) Build() hardtypes.Deposit { return builder.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 DepositBuilder) WithSourceShares(denom string, shares int64) DepositBuilder {
|
||||
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")
|
||||
}
|
||||
@ -328,7 +328,7 @@ func (builder DepositBuilder) WithSourceShares(denom string, shares int64) Depos
|
||||
}
|
||||
|
||||
// WithArbitrarySourceShares adds arbitrary deposit amounts and indexes for each specified denom.
|
||||
func (builder DepositBuilder) WithArbitrarySourceShares(denoms ...string) DepositBuilder {
|
||||
func (builder HardDepositBuilder) WithArbitrarySourceShares(denoms ...string) HardDepositBuilder {
|
||||
const arbitraryShares = 1e9
|
||||
for _, denom := range denoms {
|
||||
builder = builder.WithSourceShares(denom, arbitraryShares)
|
||||
|
@ -29,7 +29,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreRemovedForDeno
|
||||
|
||||
// remove one denom from the indexes already in the deposit
|
||||
expectedIndexes := claim.SupplyRewardIndexes[1:]
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(expectedIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -50,7 +50,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreAddedForNewlyS
|
||||
globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes)
|
||||
suite.storeGlobalSupplyIndexes(globalIndexes)
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -72,7 +72,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreUnchangedWhenS
|
||||
// UpdateHardSupplyIndexDenoms should ignore the new values.
|
||||
suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes))
|
||||
|
||||
deposit := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
|
||||
Build()
|
||||
|
||||
@ -95,7 +95,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestEmptyClaimIndexesAreAddedForN
|
||||
// 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 := NewDepositBuilder(claim.Owner).
|
||||
deposit := NewHardDepositBuilder(claim.Owner).
|
||||
WithArbitrarySourceShares(depositedDenoms...).
|
||||
Build()
|
||||
|
||||
|
@ -92,6 +92,11 @@ func (suite *unitTester) storeGlobalSwapIndexes(indexes types.MultiRewardIndexes
|
||||
suite.keeper.SetSwapRewardIndexes(suite.ctx, i.CollateralType, i.RewardIndexes)
|
||||
}
|
||||
}
|
||||
func (suite *unitTester) storeGlobalSavingsIndexes(indexes types.MultiRewardIndexes) {
|
||||
for _, i := range indexes {
|
||||
suite.keeper.SetSavingsRewardIndexes(suite.ctx, i.CollateralType, i.RewardIndexes)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *unitTester) storeHardClaim(claim types.HardLiquidityProviderClaim) {
|
||||
suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, claim)
|
||||
@ -102,6 +107,9 @@ func (suite *unitTester) storeDelegatorClaim(claim types.DelegatorClaim) {
|
||||
func (suite *unitTester) storeSwapClaim(claim types.SwapClaim) {
|
||||
suite.keeper.SetSwapClaim(suite.ctx, claim)
|
||||
}
|
||||
func (suite *unitTester) storeSavingsClaim(claim types.SavingsClaim) {
|
||||
suite.keeper.SetSavingsClaim(suite.ctx, claim)
|
||||
}
|
||||
|
||||
// fakeParamSubspace is a stub paramSpace to simplify keeper unit test setup.
|
||||
type fakeParamSubspace struct {
|
||||
|
@ -21,13 +21,20 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
|
||||
}
|
||||
|
||||
currDeposit, foundDeposit := k.GetDeposit(ctx, depositor)
|
||||
amount := coins
|
||||
|
||||
deposit := types.NewDeposit(depositor, coins)
|
||||
if foundDeposit {
|
||||
amount = amount.Add(currDeposit.Amount...)
|
||||
deposit.Amount = deposit.Amount.Add(currDeposit.Amount...)
|
||||
k.hooks.BeforeSavingsDepositModified(ctx, deposit, setDifference(getDenoms(coins), getDenoms(deposit.Amount)))
|
||||
|
||||
}
|
||||
deposit := types.NewDeposit(depositor, amount)
|
||||
|
||||
k.SetDeposit(ctx, deposit)
|
||||
|
||||
if !foundDeposit {
|
||||
k.hooks.AfterSavingsDepositCreated(ctx, deposit)
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSavingsDeposit,
|
||||
@ -43,7 +50,7 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
|
||||
func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error {
|
||||
for _, coin := range coins {
|
||||
supported := k.IsDenomSupported(ctx, coin.Denom)
|
||||
if supported == false {
|
||||
if !supported {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, ": %s", coin.Denom)
|
||||
}
|
||||
}
|
||||
@ -56,3 +63,27 @@ func (k Keeper) GetTotalDeposited(ctx sdk.Context, depositDenom string) (total s
|
||||
macc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
|
||||
return k.bankKeeper.GetBalance(ctx, macc.GetAddress(), depositDenom).Amount
|
||||
}
|
||||
|
||||
// Set setDifference: A - B
|
||||
func setDifference(a, b []string) (diff []string) {
|
||||
m := make(map[string]bool)
|
||||
|
||||
for _, item := range b {
|
||||
m[item] = true
|
||||
}
|
||||
|
||||
for _, item := range a {
|
||||
if _, ok := m[item]; !ok {
|
||||
diff = append(diff, item)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDenoms(coins sdk.Coins) []string {
|
||||
denoms := []string{}
|
||||
for _, coin := range coins {
|
||||
denoms = append(denoms, coin.Denom)
|
||||
}
|
||||
return denoms
|
||||
}
|
||||
|
29
x/savings/keeper/diff_test.go
Normal file
29
x/savings/keeper/diff_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSetDiff(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setA []string
|
||||
setB []string
|
||||
expected []string
|
||||
}{
|
||||
{"empty", []string{}, []string{}, []string(nil)},
|
||||
{"diff equal sets", []string{"busd", "usdx"}, []string{"busd", "usdx"}, []string(nil)},
|
||||
{"diff set empty", []string{"bnb", "ukava", "usdx"}, []string{}, []string{"bnb", "ukava", "usdx"}},
|
||||
{"input set empty", []string{}, []string{"bnb", "ukava", "usdx"}, []string(nil)},
|
||||
{"diff set with common elements", []string{"bnb", "btcb", "usdx", "xrpb"}, []string{"bnb", "usdx"}, []string{"btcb", "xrpb"}},
|
||||
{"diff set with all common elements", []string{"bnb", "usdx"}, []string{"bnb", "btcb", "usdx", "xrpb"}, []string(nil)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, setDifference(tt.setA, tt.setB))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user