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:
Denali Marsh 2022-04-21 16:19:03 +02:00 committed by GitHub
parent c2e53f2d00
commit eaeaf20e83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 578 additions and 37 deletions

View File

@ -168,10 +168,10 @@ func (h Hooks) BeforePoolDepositModified(ctx sdk.Context, poolID string, deposit
// AfterSavingsDepositCreated function that runs after a deposit is created // AfterSavingsDepositCreated function that runs after a deposit is created
func (h Hooks) AfterSavingsDepositCreated(ctx sdk.Context, deposit savingstypes.Deposit) { 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 // BeforeSavingsDepositModified function that runs before a deposit is modified
func (h Hooks) BeforeSavingsDepositModified(ctx sdk.Context, deposit savingstypes.Deposit, incomingDenoms []string) { func (h Hooks) BeforeSavingsDepositModified(ctx sdk.Context, deposit savingstypes.Deposit, incomingDenoms []string) {
// TODO: SynchronizeSavingsReward h.k.SynchronizeSavingsReward(ctx, deposit, incomingDenoms)
} }

View File

@ -38,30 +38,54 @@ func (k Keeper) AccumulateSavingsRewards(ctx sdk.Context, rewardPeriod types.Mul
} }
} }
func (k Keeper) SynchronizeSavingsClaim(ctx sdk.Context, owner sdk.AccAddress) { // InitializeSavingsReward initializes a savings claim by creating the claim and
deposit, found := k.savingsKeeper.GetDeposit(ctx, owner) // setting the reward factor indexes
func (k Keeper) InitializeSavingsReward(ctx sdk.Context, deposit savingstypes.Deposit) {
claim, found := k.GetSavingsClaim(ctx, deposit.Depositor)
if !found { 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 // SynchronizeSavingsReward updates the claim object by adding any accumulated rewards
// and updating the reward index value // 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) claim, found := k.GetSavingsClaim(ctx, deposit.Depositor)
if !found { if !found {
return return
} }
// Source shares for savings deposits is the deposit amount // Set the reward factor on claim to the global reward factor for each incoming denom
for _, coin := range deposit.Amount { for _, denom := range incomingDenoms {
claim = k.synchronizeSingleSavingsReward(ctx, claim, coin.Denom, coin.Amount.ToDec()) 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) 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. // 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. // 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 { 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 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{})
}

View 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)
}

View 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
}

View File

@ -32,7 +32,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimExi
globalIndexes := nonEmptyMultiRewardIndexes globalIndexes := nonEmptyMultiRewardIndexes
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build() Build()
@ -46,7 +46,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetWhenClaimDoe
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
owner := arbitraryAddress() owner := arbitraryAddress()
deposit := NewDepositBuilder(owner). deposit := NewHardDepositBuilder(owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build() Build()
@ -66,7 +66,7 @@ func (suite *InitializeHardSupplyRewardTests) TestClaimIndexesAreSetEmptyForMiss
// This happens when a deposit denom has no rewards associated with it. // This happens when a deposit denom has no rewards associated with it.
expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes) expectedIndexes := appendUniqueEmptyMultiRewardIndex(globalIndexes)
depositedDenoms := extractCollateralTypes(expectedIndexes) depositedDenoms := extractCollateralTypes(expectedIndexes)
deposit := NewDepositBuilder(owner). deposit := NewHardDepositBuilder(owner).
WithArbitrarySourceShares(depositedDenoms...). WithArbitrarySourceShares(depositedDenoms...).
Build() Build()

View File

@ -32,7 +32,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenGlo
globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes) globalIndexes := increaseAllRewardFactors(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
Build() Build()
@ -56,7 +56,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUnchangedWhenG
suite.storeGlobalSupplyIndexes(unchangingIndexes) suite.storeGlobalSupplyIndexes(unchangingIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(unchangingIndexes)...).
Build() Build()
@ -80,7 +80,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes) globalIndexes := appendUniqueMultiRewardIndex(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build() Build()
@ -105,7 +105,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestClaimIndexesAreUpdatedWhenNew
globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes) globalIndexes := appendUniqueRewardIndexToFirstItem(nonEmptyMultiRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build() Build()
@ -154,7 +154,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenGlobal
}, },
}) })
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithSourceShares("depositdenom", 1e9). WithSourceShares("depositdenom", 1e9).
Build() Build()
@ -216,7 +216,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
} }
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithSourceShares("rewarded", 1e9). WithSourceShares("rewarded", 1e9).
WithSourceShares("newlyrewarded", 1e9). WithSourceShares("newlyrewarded", 1e9).
Build() Build()
@ -274,7 +274,7 @@ func (suite *SynchronizeHardSupplyRewardTests) TestRewardIsIncrementedWhenNewRew
} }
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithSourceShares("deposited", 1e9). WithSourceShares("deposited", 1e9).
Build() 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. // 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 hardtypes.Deposit
} }
// NewDepositBuilder creates a DepositBuilder containing an empty deposit. // NewHardDepositBuilder creates a HardDepositBuilder containing an empty deposit.
func NewDepositBuilder(depositor sdk.AccAddress) DepositBuilder { func NewHardDepositBuilder(depositor sdk.AccAddress) HardDepositBuilder {
return DepositBuilder{ return HardDepositBuilder{
Deposit: hardtypes.Deposit{ Deposit: hardtypes.Deposit{
Depositor: depositor, Depositor: depositor,
}} }}
} }
// Build assembles and returns the final deposit. // 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. // 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. // 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()) { if !builder.Amount.AmountOf(denom).Equal(sdk.ZeroInt()) {
panic("adding to amount with existing denom not implemented") 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. // 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 const arbitraryShares = 1e9
for _, denom := range denoms { for _, denom := range denoms {
builder = builder.WithSourceShares(denom, arbitraryShares) builder = builder.WithSourceShares(denom, arbitraryShares)

View File

@ -29,7 +29,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreRemovedForDeno
// remove one denom from the indexes already in the deposit // remove one denom from the indexes already in the deposit
expectedIndexes := claim.SupplyRewardIndexes[1:] expectedIndexes := claim.SupplyRewardIndexes[1:]
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(expectedIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(expectedIndexes)...).
Build() Build()
@ -50,7 +50,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreAddedForNewlyS
globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes) globalIndexes := appendUniqueMultiRewardIndex(claim.SupplyRewardIndexes)
suite.storeGlobalSupplyIndexes(globalIndexes) suite.storeGlobalSupplyIndexes(globalIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(globalIndexes)...).
Build() Build()
@ -72,7 +72,7 @@ func (suite *UpdateHardSupplyIndexDenomsTests) TestClaimIndexesAreUnchangedWhenS
// UpdateHardSupplyIndexDenoms should ignore the new values. // UpdateHardSupplyIndexDenoms should ignore the new values.
suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes)) suite.storeGlobalSupplyIndexes(increaseAllRewardFactors(claim.SupplyRewardIndexes))
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...). WithArbitrarySourceShares(extractCollateralTypes(claim.SupplyRewardIndexes)...).
Build() 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 // add a denom to the deposited amount that is not in the global or claim's indexes
expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.SupplyRewardIndexes) expectedIndexes := appendUniqueEmptyMultiRewardIndex(claim.SupplyRewardIndexes)
depositedDenoms := extractCollateralTypes(expectedIndexes) depositedDenoms := extractCollateralTypes(expectedIndexes)
deposit := NewDepositBuilder(claim.Owner). deposit := NewHardDepositBuilder(claim.Owner).
WithArbitrarySourceShares(depositedDenoms...). WithArbitrarySourceShares(depositedDenoms...).
Build() Build()

View File

@ -92,6 +92,11 @@ func (suite *unitTester) storeGlobalSwapIndexes(indexes types.MultiRewardIndexes
suite.keeper.SetSwapRewardIndexes(suite.ctx, i.CollateralType, i.RewardIndexes) 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) { func (suite *unitTester) storeHardClaim(claim types.HardLiquidityProviderClaim) {
suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, claim) suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, claim)
@ -102,6 +107,9 @@ func (suite *unitTester) storeDelegatorClaim(claim types.DelegatorClaim) {
func (suite *unitTester) storeSwapClaim(claim types.SwapClaim) { func (suite *unitTester) storeSwapClaim(claim types.SwapClaim) {
suite.keeper.SetSwapClaim(suite.ctx, claim) 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. // fakeParamSubspace is a stub paramSpace to simplify keeper unit test setup.
type fakeParamSubspace struct { type fakeParamSubspace struct {

View File

@ -21,13 +21,20 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
} }
currDeposit, foundDeposit := k.GetDeposit(ctx, depositor) currDeposit, foundDeposit := k.GetDeposit(ctx, depositor)
amount := coins
deposit := types.NewDeposit(depositor, coins)
if foundDeposit { 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) k.SetDeposit(ctx, deposit)
if !foundDeposit {
k.hooks.AfterSavingsDepositCreated(ctx, deposit)
}
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeSavingsDeposit, 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 { func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error {
for _, coin := range coins { for _, coin := range coins {
supported := k.IsDenomSupported(ctx, coin.Denom) supported := k.IsDenomSupported(ctx, coin.Denom)
if supported == false { if !supported {
return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, ": %s", coin.Denom) 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) macc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
return k.bankKeeper.GetBalance(ctx, macc.GetAddress(), depositDenom).Amount 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
}

View 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))
})
}
}