mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 14:37:27 +00:00 
			
		
		
		
	Add liquid staking reward redistribution via incentive (#1308)
* wip Add claim * Add distr keeper and claiming * Add claim test * Update claim test with failures * wip Add staking rewards * -S Fix savings to earn incentive methods * Use a single accural time for all earn incentives * Add additional required liquid methods * Update genesis to only include 1 accrual time for earn * Revert "Update genesis to only include 1 accrual time for earn" This reverts commit cc7e35347298681c0c8a4a0b9bf9b9b296c25531. * Revert "Use a single accural time for all earn incentives" This reverts commit aeb49c4622d4e3d99dc6421c8830932b1b546be9. * Update tests with incentive distribution * Add earn to incentive rewards query * add earn cli tx * Update claim example to use ukava large * Use underlying ukava to determine proportional reward amount * Rename liquid methods to reflect derivative value * Add tests for derivative values * Return error to panic in BeginBlocker Co-authored-by: karzak <kjydavis3@gmail.com>
This commit is contained in:
		
							parent
							
								
									ac96bb9c18
								
							
						
					
					
						commit
						6ef9bab67d
					
				@ -594,6 +594,7 @@ func NewApp(
 | 
				
			|||||||
		app.accountKeeper,
 | 
							app.accountKeeper,
 | 
				
			||||||
		app.bankKeeper,
 | 
							app.bankKeeper,
 | 
				
			||||||
		&app.stakingKeeper,
 | 
							&app.stakingKeeper,
 | 
				
			||||||
 | 
							&app.distrKeeper,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	savingsKeeper := savingskeeper.NewKeeper(
 | 
						savingsKeeper := savingskeeper.NewKeeper(
 | 
				
			||||||
		appCodec,
 | 
							appCodec,
 | 
				
			||||||
@ -625,8 +626,7 @@ func NewApp(
 | 
				
			|||||||
		app.stakingKeeper,
 | 
							app.stakingKeeper,
 | 
				
			||||||
		&swapKeeper,
 | 
							&swapKeeper,
 | 
				
			||||||
		&savingsKeeper,
 | 
							&savingsKeeper,
 | 
				
			||||||
		// TODO: Liquid keeper
 | 
							&app.liquidKeeper,
 | 
				
			||||||
		nil,
 | 
					 | 
				
			||||||
		&earnKeeper,
 | 
							&earnKeeper,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	app.routerKeeper = routerkeeper.NewKeeper(
 | 
						app.routerKeeper = routerkeeper.NewKeeper(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
package incentive
 | 
					package incentive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/keeper"
 | 
						"github.com/kava-labs/kava/x/incentive/keeper"
 | 
				
			||||||
@ -29,6 +31,8 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
 | 
				
			|||||||
		k.AccumulateSavingsRewards(ctx, rp)
 | 
							k.AccumulateSavingsRewards(ctx, rp)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, rp := range params.EarnRewardPeriods {
 | 
						for _, rp := range params.EarnRewardPeriods {
 | 
				
			||||||
		k.AccumulateEarnRewards(ctx, rp)
 | 
							if err := k.AccumulateEarnRewards(ctx, rp); err != nil {
 | 
				
			||||||
 | 
								panic(fmt.Sprintf("failed to accumulate earn rewards: %s", err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -159,6 +159,11 @@ func queryRewardsCmd() *cobra.Command {
 | 
				
			|||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return err
 | 
										return err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									earnClaims, err := executeEarnRewardsQuery(cliCtx, params)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if len(hardClaims) > 0 {
 | 
									if len(hardClaims) > 0 {
 | 
				
			||||||
					if err := cliCtx.PrintObjectLegacy(hardClaims); err != nil {
 | 
										if err := cliCtx.PrintObjectLegacy(hardClaims); err != nil {
 | 
				
			||||||
						return err
 | 
											return err
 | 
				
			||||||
@ -184,6 +189,11 @@ func queryRewardsCmd() *cobra.Command {
 | 
				
			|||||||
						return err
 | 
											return err
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									if len(earnClaims) > 0 {
 | 
				
			||||||
 | 
										if err := cliCtx.PrintObjectLegacy(earnClaims); err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@ func GetTxCmd() *cobra.Command {
 | 
				
			|||||||
		getCmdClaimDelegator(),
 | 
							getCmdClaimDelegator(),
 | 
				
			||||||
		getCmdClaimSwap(),
 | 
							getCmdClaimSwap(),
 | 
				
			||||||
		getCmdClaimSavings(),
 | 
							getCmdClaimSavings(),
 | 
				
			||||||
 | 
							getCmdClaimEarn(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, cmd := range cmds {
 | 
						for _, cmd := range cmds {
 | 
				
			||||||
@ -209,3 +210,35 @@ func getCmdClaimSavings() *cobra.Command {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return cmd
 | 
						return cmd
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getCmdClaimEarn() *cobra.Command {
 | 
				
			||||||
 | 
						var denomsToClaim map[string]string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := &cobra.Command{
 | 
				
			||||||
 | 
							Use:     "claim-earn",
 | 
				
			||||||
 | 
							Short:   "claim sender's earn rewards using given multipliers",
 | 
				
			||||||
 | 
							Long:    `Claim sender's outstanding earn rewards using given multipliers`,
 | 
				
			||||||
 | 
							Example: fmt.Sprintf(`  $ %s tx %s claim-earn --%s ukava=large`, version.AppName, types.ModuleName, multiplierFlag),
 | 
				
			||||||
 | 
							Args:    cobra.NoArgs,
 | 
				
			||||||
 | 
							RunE: func(cmd *cobra.Command, args []string) error {
 | 
				
			||||||
 | 
								cliCtx, err := client.GetClientTxContext(cmd)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sender := cliCtx.GetFromAddress()
 | 
				
			||||||
 | 
								selections := types.NewSelectionsFromMap(denomsToClaim)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								msg := types.NewMsgClaimEarnReward(sender.String(), selections)
 | 
				
			||||||
 | 
								if err := msg.ValidateBasic(); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), &msg)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
 | 
				
			||||||
 | 
						if err := cmd.MarkFlagRequired(multiplierFlag); err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cmd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +1,24 @@
 | 
				
			|||||||
package keeper
 | 
					package keeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	earntypes "github.com/kava-labs/kava/x/earn/types"
 | 
						earntypes "github.com/kava-labs/kava/x/earn/types"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AccumulateEarnRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
 | 
					// AccumulateEarnRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
 | 
				
			||||||
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
 | 
					// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
 | 
				
			||||||
func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
 | 
					func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
 | 
				
			||||||
	if rewardPeriod.CollateralType == "bkava" {
 | 
						if rewardPeriod.CollateralType == "bkava" {
 | 
				
			||||||
		k.accumulateEarnBkavaRewards(ctx, rewardPeriod)
 | 
							return k.accumulateEarnBkavaRewards(ctx, rewardPeriod)
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.accumulateEarnRewards(
 | 
						k.accumulateEarnRewards(
 | 
				
			||||||
@ -27,6 +28,8 @@ func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiR
 | 
				
			|||||||
		rewardPeriod.End,
 | 
							rewardPeriod.End,
 | 
				
			||||||
		sdk.NewDecCoinsFromCoins(rewardPeriod.RewardsPerSecond...),
 | 
							sdk.NewDecCoinsFromCoins(rewardPeriod.RewardsPerSecond...),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetProportionalRewardsPerSecond(
 | 
					func GetProportionalRewardsPerSecond(
 | 
				
			||||||
@ -59,16 +62,13 @@ func GetProportionalRewardsPerSecond(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// accumulateEarnBkavaRewards does the same as AccumulateEarnRewards but for
 | 
					// accumulateEarnBkavaRewards does the same as AccumulateEarnRewards but for
 | 
				
			||||||
// *all* bkava vaults.
 | 
					// *all* bkava vaults.
 | 
				
			||||||
func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
 | 
					func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
 | 
				
			||||||
	// TODO: Get staking rewards and distribute
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// All bkava vault denoms
 | 
						// All bkava vault denoms
 | 
				
			||||||
	bkavaVaultsDenoms := make(map[string]bool)
 | 
						bkavaVaultsDenoms := make(map[string]bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// bkava vault denoms from earn records (non-empty vaults)
 | 
						// bkava vault denoms from earn records (non-empty vaults)
 | 
				
			||||||
	k.earnKeeper.IterateVaultRecords(ctx, func(record earntypes.VaultRecord) (stop bool) {
 | 
						k.earnKeeper.IterateVaultRecords(ctx, func(record earntypes.VaultRecord) (stop bool) {
 | 
				
			||||||
		// TODO: Replace with single bkava denom check method from liquid
 | 
							if k.liquidKeeper.IsDerivativeDenom(ctx, record.TotalShares.Denom) {
 | 
				
			||||||
		if strings.HasPrefix(record.TotalShares.Denom, "bkava-") {
 | 
					 | 
				
			||||||
			bkavaVaultsDenoms[record.TotalShares.Denom] = true
 | 
								bkavaVaultsDenoms[record.TotalShares.Denom] = true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,14 +78,17 @@ func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.M
 | 
				
			|||||||
	// bkava vault denoms from past incentive indexes, may include vaults
 | 
						// bkava vault denoms from past incentive indexes, may include vaults
 | 
				
			||||||
	// that were fully withdrawn.
 | 
						// that were fully withdrawn.
 | 
				
			||||||
	k.IterateEarnRewardIndexes(ctx, func(vaultDenom string, indexes types.RewardIndexes) (stop bool) {
 | 
						k.IterateEarnRewardIndexes(ctx, func(vaultDenom string, indexes types.RewardIndexes) (stop bool) {
 | 
				
			||||||
		if strings.HasPrefix(vaultDenom, "bkava-") {
 | 
							if k.liquidKeeper.IsDerivativeDenom(ctx, vaultDenom) {
 | 
				
			||||||
			bkavaVaultsDenoms[vaultDenom] = true
 | 
								bkavaVaultsDenoms[vaultDenom] = true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	totalBkavaSupply := k.liquidKeeper.GetTotalDerivativeSupply(ctx)
 | 
						totalBkavaValue, err := k.liquidKeeper.GetTotalDerivativeValue(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	i := 0
 | 
						i := 0
 | 
				
			||||||
	sortedBkavaVaultsDenoms := make([]string, len(bkavaVaultsDenoms))
 | 
						sortedBkavaVaultsDenoms := make([]string, len(bkavaVaultsDenoms))
 | 
				
			||||||
@ -99,18 +102,110 @@ func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.M
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Accumulate rewards for each bkava vault.
 | 
						// Accumulate rewards for each bkava vault.
 | 
				
			||||||
	for _, bkavaDenom := range sortedBkavaVaultsDenoms {
 | 
						for _, bkavaDenom := range sortedBkavaVaultsDenoms {
 | 
				
			||||||
		k.accumulateEarnRewards(
 | 
							derivativeValue, err := k.liquidKeeper.GetDerivativeValue(ctx, bkavaDenom)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							k.accumulateBkavaEarnRewards(
 | 
				
			||||||
			ctx,
 | 
								ctx,
 | 
				
			||||||
			bkavaDenom,
 | 
								bkavaDenom,
 | 
				
			||||||
			rewardPeriod.Start,
 | 
								rewardPeriod.Start,
 | 
				
			||||||
			rewardPeriod.End,
 | 
								rewardPeriod.End,
 | 
				
			||||||
			GetProportionalRewardsPerSecond(
 | 
								GetProportionalRewardsPerSecond(
 | 
				
			||||||
				rewardPeriod,
 | 
									rewardPeriod,
 | 
				
			||||||
				totalBkavaSupply,
 | 
									totalBkavaValue.Amount,
 | 
				
			||||||
				k.liquidKeeper.GetDerivativeSupply(ctx, bkavaDenom),
 | 
									derivativeValue.Amount,
 | 
				
			||||||
			),
 | 
								),
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k Keeper) accumulateBkavaEarnRewards(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						collateralType string,
 | 
				
			||||||
 | 
						periodStart time.Time,
 | 
				
			||||||
 | 
						periodEnd time.Time,
 | 
				
			||||||
 | 
						periodRewardsPerSecond sdk.DecCoins,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						// Collect staking rewards for this validator, does not have any start/end
 | 
				
			||||||
 | 
						// period time restrictions.
 | 
				
			||||||
 | 
						stakingRewards := k.collectDerivativeStakingRewards(ctx, collateralType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Collect incentive rewards
 | 
				
			||||||
 | 
						// **Total rewards** for vault per second, NOT per share
 | 
				
			||||||
 | 
						perSecondRewards := k.collectPerSecondRewards(
 | 
				
			||||||
 | 
							ctx,
 | 
				
			||||||
 | 
							collateralType,
 | 
				
			||||||
 | 
							periodStart,
 | 
				
			||||||
 | 
							periodEnd,
 | 
				
			||||||
 | 
							periodRewardsPerSecond,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// **Total rewards** for vault per second, NOT per share
 | 
				
			||||||
 | 
						rewards := stakingRewards.Add(perSecondRewards...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Distribute rewards by incrementing indexes
 | 
				
			||||||
 | 
						indexes, found := k.GetEarnRewardIndexes(ctx, collateralType)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							indexes = types.RewardIndexes{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						totalSourceShares := k.getEarnTotalSourceShares(ctx, collateralType)
 | 
				
			||||||
 | 
						var increment types.RewardIndexes
 | 
				
			||||||
 | 
						if totalSourceShares.GT(sdk.ZeroDec()) {
 | 
				
			||||||
 | 
							// Divide total rewards by total shares to get the reward **per share**
 | 
				
			||||||
 | 
							// Leave as nil if no source shares
 | 
				
			||||||
 | 
							increment = types.NewRewardIndexesFromCoins(rewards).Quo(totalSourceShares)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						updatedIndexes := indexes.Add(increment)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(updatedIndexes) > 0 {
 | 
				
			||||||
 | 
							// the store panics when setting empty or nil indexes
 | 
				
			||||||
 | 
							k.SetEarnRewardIndexes(ctx, collateralType, updatedIndexes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k Keeper) collectDerivativeStakingRewards(ctx sdk.Context, collateralType string) sdk.DecCoins {
 | 
				
			||||||
 | 
						rewards, err := k.liquidKeeper.CollectStakingRewardsByDenom(ctx, collateralType, types.IncentiveMacc)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if !errors.Is(err, distrtypes.ErrNoValidatorDistInfo) &&
 | 
				
			||||||
 | 
								!errors.Is(err, distrtypes.ErrEmptyDelegationDistInfo) {
 | 
				
			||||||
 | 
								panic(fmt.Sprintf("failed to collect staking rewards for %s: %s", collateralType, err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// otherwise there's no validator or delegation yet
 | 
				
			||||||
 | 
							rewards = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sdk.NewDecCoinsFromCoins(rewards...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k Keeper) collectPerSecondRewards(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						collateralType string,
 | 
				
			||||||
 | 
						periodStart time.Time,
 | 
				
			||||||
 | 
						periodEnd time.Time,
 | 
				
			||||||
 | 
						periodRewardsPerSecond sdk.DecCoins,
 | 
				
			||||||
 | 
					) sdk.DecCoins {
 | 
				
			||||||
 | 
						previousAccrualTime, found := k.GetEarnRewardAccrualTime(ctx, collateralType)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							previousAccrualTime = ctx.BlockTime()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rewards, accumulatedTo := types.CalculatePerSecondRewards(
 | 
				
			||||||
 | 
							periodStart,
 | 
				
			||||||
 | 
							periodEnd,
 | 
				
			||||||
 | 
							periodRewardsPerSecond,
 | 
				
			||||||
 | 
							previousAccrualTime,
 | 
				
			||||||
 | 
							ctx.BlockTime(),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.SetEarnRewardAccrualTime(ctx, collateralType, accumulatedTo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Don't need to move funds as they're assumed to be in the IncentiveMacc module account already.
 | 
				
			||||||
 | 
						return rewards
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k Keeper) accumulateEarnRewards(
 | 
					func (k Keeper) accumulateEarnRewards(
 | 
				
			||||||
 | 
				
			|||||||
@ -94,13 +94,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
	vaultDenom1 := "bkava-meow"
 | 
						vaultDenom1 := "bkava-meow"
 | 
				
			||||||
	vaultDenom2 := "bkava-woof"
 | 
						vaultDenom2 := "bkava-woof"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	earnKeeper := newFakeEarnKeeper().
 | 
						earnKeeper := newFakeEarnKeeper().
 | 
				
			||||||
		addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("800000"))).
 | 
							addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("800000"))).
 | 
				
			||||||
		addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("200000")))
 | 
							addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("200000")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	liquidKeeper := newFakeLiquidKeeper().
 | 
						liquidKeeper := newFakeLiquidKeeper().
 | 
				
			||||||
		addDerivative(vaultDenom1, i(800000)).
 | 
							addDerivative(suite.ctx, vaultDenom1, i(800000)).
 | 
				
			||||||
		addDerivative(vaultDenom2, i(200000))
 | 
							addDerivative(suite.ctx, vaultDenom2, i(200000))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
						suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -134,7 +137,6 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.storeGlobalEarnIndexes(globalIndexes)
 | 
						suite.storeGlobalEarnIndexes(globalIndexes)
 | 
				
			||||||
	previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
					 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -164,7 +166,8 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			CollateralType: "ukava",
 | 
								CollateralType: "ukava",
 | 
				
			||||||
			RewardFactor:   d("3.64"),
 | 
								RewardFactor: d("3.64"). // base incentive
 | 
				
			||||||
 | 
												Add(d("360")), // staking rewards, 10% of total bkava per second
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -179,16 +182,20 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
	vaultDenom1Supply := i(800000)
 | 
						vaultDenom1Supply := i(800000)
 | 
				
			||||||
	vaultDenom2Supply := i(200000)
 | 
						vaultDenom2Supply := i(200000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	liquidKeeper := newFakeLiquidKeeper().
 | 
						previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
		addDerivative(vaultDenom1, vaultDenom1Supply).
 | 
						suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
 | 
				
			||||||
		addDerivative(vaultDenom2, vaultDenom2Supply)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						liquidKeeper := newFakeLiquidKeeper().
 | 
				
			||||||
 | 
							addDerivative(suite.ctx, vaultDenom1, vaultDenom1Supply).
 | 
				
			||||||
 | 
							addDerivative(suite.ctx, vaultDenom2, vaultDenom2Supply)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vault1Shares := d("700000")
 | 
				
			||||||
	vault2Shares := d("100000")
 | 
						vault2Shares := d("100000")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// More bkava minted than deposited into earn
 | 
						// More bkava minted than deposited into earn
 | 
				
			||||||
	// Rewards are higher per-share as a result
 | 
						// Rewards are higher per-share as a result
 | 
				
			||||||
	earnKeeper := newFakeEarnKeeper().
 | 
						earnKeeper := newFakeEarnKeeper().
 | 
				
			||||||
		addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("700000"))).
 | 
							addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, vault1Shares)).
 | 
				
			||||||
		addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, vault2Shares))
 | 
							addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, vault2Shares))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
						suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
				
			||||||
@ -223,7 +230,7 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.storeGlobalEarnIndexes(globalIndexes)
 | 
						suite.storeGlobalEarnIndexes(globalIndexes)
 | 
				
			||||||
	previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
					
 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -252,7 +259,12 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			CollateralType: "ukava",
 | 
								CollateralType: "ukava",
 | 
				
			||||||
			RewardFactor:   d("4.154285714285714286"),
 | 
								RewardFactor: d("4.154285714285714286"). // base incentive
 | 
				
			||||||
 | 
														Add(vaultDenom1Supply.ToDec(). // staking rewards
 | 
				
			||||||
 | 
																		QuoInt64(10).
 | 
				
			||||||
 | 
																		MulInt64(3600).
 | 
				
			||||||
 | 
																		Quo(vault1Shares),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -278,27 +290,15 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			CollateralType: "ukava",
 | 
								CollateralType: "ukava",
 | 
				
			||||||
			RewardFactor:   d("7.24"),
 | 
								RewardFactor: d("7.24").
 | 
				
			||||||
 | 
									Add(vaultDenom2Supply.ToDec().
 | 
				
			||||||
 | 
										QuoInt64(10).
 | 
				
			||||||
 | 
										MulInt64(3600).
 | 
				
			||||||
 | 
										Quo(vault2Shares),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storedIndexesEqual(vaultDenom2, vault2expectedIndexes)
 | 
						suite.storedIndexesEqual(vaultDenom2, vault2expectedIndexes)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Verify math described above
 | 
					 | 
				
			||||||
	totalVault2DistributedUkava := i(int64(time.Hour.Seconds())).
 | 
					 | 
				
			||||||
		ToDec().
 | 
					 | 
				
			||||||
		Mul(rewardPeriod.RewardsPerSecond.AmountOf("ukava").ToDec()).
 | 
					 | 
				
			||||||
		// 20% of total rewards
 | 
					 | 
				
			||||||
		// vault 2 supply / (vault 1 supply + vault 2 supply)
 | 
					 | 
				
			||||||
		Mul(
 | 
					 | 
				
			||||||
			vaultDenom2Supply.ToDec().
 | 
					 | 
				
			||||||
				Quo(vaultDenom1Supply.Add(vaultDenom2Supply).ToDec()),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	totalVault2ClaimableRewards := vault2expectedIndexes[1].
 | 
					 | 
				
			||||||
		RewardFactor.Sub(d("0.04")). // Rewards per share for 1 hr, excluding the starting value
 | 
					 | 
				
			||||||
		Mul(vault2Shares)            // * Shares in vault to get total rewards for entire vault
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	suite.Equal(totalVault2DistributedUkava, totalVault2ClaimableRewards)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
 | 
					func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
 | 
				
			||||||
@ -350,13 +350,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn
 | 
				
			|||||||
	vaultDenom1 := "bkava-meow"
 | 
						vaultDenom1 := "bkava-meow"
 | 
				
			||||||
	vaultDenom2 := "bkava-woof"
 | 
						vaultDenom2 := "bkava-woof"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	earnKeeper := newFakeEarnKeeper().
 | 
						earnKeeper := newFakeEarnKeeper().
 | 
				
			||||||
		addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
 | 
							addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
 | 
				
			||||||
		addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
 | 
							addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	liquidKeeper := newFakeLiquidKeeper().
 | 
						liquidKeeper := newFakeLiquidKeeper().
 | 
				
			||||||
		addDerivative(vaultDenom1, i(1000000)).
 | 
							addDerivative(suite.ctx, vaultDenom1, i(1000000)).
 | 
				
			||||||
		addDerivative(vaultDenom2, i(1000000))
 | 
							addDerivative(suite.ctx, vaultDenom2, i(1000000))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
						suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -389,12 +392,10 @@ func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	suite.storeGlobalEarnIndexes(previousIndexes)
 | 
						suite.storeGlobalEarnIndexes(previousIndexes)
 | 
				
			||||||
	previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
					
 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
				
			||||||
	suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	period := types.NewMultiRewardPeriod(
 | 
						period := types.NewMultiRewardPeriod(
 | 
				
			||||||
		true,
 | 
							true,
 | 
				
			||||||
		"bkava",
 | 
							"bkava",
 | 
				
			||||||
@ -585,13 +586,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bka
 | 
				
			|||||||
	vaultDenom1 := "bkava-meow"
 | 
						vaultDenom1 := "bkava-meow"
 | 
				
			||||||
	vaultDenom2 := "bkava-woof"
 | 
						vaultDenom2 := "bkava-woof"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	earnKeeper := newFakeEarnKeeper().
 | 
						earnKeeper := newFakeEarnKeeper().
 | 
				
			||||||
		addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
 | 
							addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
 | 
				
			||||||
		addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
 | 
							addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	liquidKeeper := newFakeLiquidKeeper().
 | 
						liquidKeeper := newFakeLiquidKeeper().
 | 
				
			||||||
		addDerivative(vaultDenom1, i(1000000)).
 | 
							addDerivative(suite.ctx, vaultDenom1, i(1000000)).
 | 
				
			||||||
		addDerivative(vaultDenom2, i(1000000))
 | 
							addDerivative(suite.ctx, vaultDenom2, i(1000000))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
						suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -603,9 +607,6 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bka
 | 
				
			|||||||
		cs(c("earn", 2000), c("ukava", 1000)),
 | 
							cs(c("earn", 2000), c("ukava", 1000)),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
					 | 
				
			||||||
	suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	suite.keeper.AccumulateEarnRewards(suite.ctx, period)
 | 
						suite.keeper.AccumulateEarnRewards(suite.ctx, period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// After the first accumulation only the current block time should be stored.
 | 
						// After the first accumulation only the current block time should be stored.
 | 
				
			||||||
@ -632,7 +633,9 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bka
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			CollateralType: "ukava",
 | 
								CollateralType: "ukava",
 | 
				
			||||||
			RewardFactor:   d("0.005"),
 | 
								// 10% of total bkava for rewards per second for 10 seconds
 | 
				
			||||||
 | 
								// 1ukava per share per second + regular 0.005ukava incentive rewards
 | 
				
			||||||
 | 
								RewardFactor: d("1.005"),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										103
									
								
								x/incentive/keeper/rewards_earn_staking_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								x/incentive/keeper/rewards_earn_staking_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					package keeper_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						earntypes "github.com/kava-labs/kava/x/earn/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/incentive/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *AccumulateEarnRewardsTests) TestStakingRewardsDistributed() {
 | 
				
			||||||
 | 
						vaultDenom1 := "bkava-meow"
 | 
				
			||||||
 | 
						vaultDenom2 := "bkava-woof"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vaultDenom1Supply := i(800000)
 | 
				
			||||||
 | 
						vaultDenom2Supply := i(200000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						liquidKeeper := newFakeLiquidKeeper().
 | 
				
			||||||
 | 
							addDerivative(suite.ctx, vaultDenom1, vaultDenom1Supply).
 | 
				
			||||||
 | 
							addDerivative(suite.ctx, vaultDenom2, vaultDenom2Supply)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vault1Shares := d("700000")
 | 
				
			||||||
 | 
						vault2Shares := d("100000")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// More bkava minted than deposited into earn
 | 
				
			||||||
 | 
						// Rewards are higher per-share as a result
 | 
				
			||||||
 | 
						earnKeeper := newFakeEarnKeeper().
 | 
				
			||||||
 | 
							addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, vault1Shares)).
 | 
				
			||||||
 | 
							addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, vault2Shares))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, nil, nil, liquidKeeper, earnKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initialVault1RewardFactor := d("0.04")
 | 
				
			||||||
 | 
						initialVault2RewardFactor := d("0.04")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						globalIndexes := types.MultiRewardIndexes{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CollateralType: vaultDenom1,
 | 
				
			||||||
 | 
								RewardIndexes: types.RewardIndexes{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										CollateralType: "ukava",
 | 
				
			||||||
 | 
										RewardFactor:   initialVault1RewardFactor,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CollateralType: vaultDenom2,
 | 
				
			||||||
 | 
								RewardIndexes: types.RewardIndexes{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										CollateralType: "ukava",
 | 
				
			||||||
 | 
										RewardFactor:   initialVault2RewardFactor,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.storeGlobalEarnIndexes(globalIndexes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
 | 
				
			||||||
 | 
						suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
 | 
				
			||||||
 | 
						suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.keeper.AccumulateEarnRewards(suite.ctx, rewardPeriod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check time and factors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.storedTimeEquals(vaultDenom1, newAccrualTime)
 | 
				
			||||||
 | 
						suite.storedTimeEquals(vaultDenom2, newAccrualTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Only contains staking rewards
 | 
				
			||||||
 | 
						suite.storedIndexesEqual(vaultDenom1, types.RewardIndexes{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CollateralType: "ukava",
 | 
				
			||||||
 | 
								RewardFactor: initialVault1RewardFactor.
 | 
				
			||||||
 | 
									Add(vaultDenom1Supply.ToDec().
 | 
				
			||||||
 | 
										QuoInt64(10).
 | 
				
			||||||
 | 
										MulInt64(3600).
 | 
				
			||||||
 | 
										Quo(vault1Shares)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.storedIndexesEqual(vaultDenom2, types.RewardIndexes{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CollateralType: "ukava",
 | 
				
			||||||
 | 
								RewardFactor: initialVault2RewardFactor.
 | 
				
			||||||
 | 
									Add(vaultDenom2Supply.ToDec().
 | 
				
			||||||
 | 
										QuoInt64(10).
 | 
				
			||||||
 | 
										MulInt64(3600).
 | 
				
			||||||
 | 
										Quo(vault2Shares)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -433,6 +433,7 @@ func (k *fakeEarnKeeper) IterateVaultRecords(
 | 
				
			|||||||
// It can be used to return values to the incentive keeper without having to initialize a full liquid keeper.
 | 
					// It can be used to return values to the incentive keeper without having to initialize a full liquid keeper.
 | 
				
			||||||
type fakeLiquidKeeper struct {
 | 
					type fakeLiquidKeeper struct {
 | 
				
			||||||
	derivatives     map[string]sdk.Int
 | 
						derivatives     map[string]sdk.Int
 | 
				
			||||||
 | 
						lastRewardClaim map[string]time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ types.LiquidKeeper = newFakeLiquidKeeper()
 | 
					var _ types.LiquidKeeper = newFakeLiquidKeeper()
 | 
				
			||||||
@ -440,11 +441,17 @@ var _ types.LiquidKeeper = newFakeLiquidKeeper()
 | 
				
			|||||||
func newFakeLiquidKeeper() *fakeLiquidKeeper {
 | 
					func newFakeLiquidKeeper() *fakeLiquidKeeper {
 | 
				
			||||||
	return &fakeLiquidKeeper{
 | 
						return &fakeLiquidKeeper{
 | 
				
			||||||
		derivatives:     map[string]sdk.Int{},
 | 
							derivatives:     map[string]sdk.Int{},
 | 
				
			||||||
 | 
							lastRewardClaim: map[string]time.Time{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *fakeLiquidKeeper) addDerivative(denom string, supply sdk.Int) *fakeLiquidKeeper {
 | 
					func (k *fakeLiquidKeeper) addDerivative(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						denom string,
 | 
				
			||||||
 | 
						supply sdk.Int,
 | 
				
			||||||
 | 
					) *fakeLiquidKeeper {
 | 
				
			||||||
	k.derivatives[denom] = supply
 | 
						k.derivatives[denom] = supply
 | 
				
			||||||
 | 
						k.lastRewardClaim[denom] = ctx.BlockTime()
 | 
				
			||||||
	return k
 | 
						return k
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -460,22 +467,56 @@ func (k *fakeLiquidKeeper) GetAllDerivativeDenoms(ctx sdk.Context) (denoms []str
 | 
				
			|||||||
	return denoms
 | 
						return denoms
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *fakeLiquidKeeper) GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int {
 | 
					func (k *fakeLiquidKeeper) GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error) {
 | 
				
			||||||
	totalSupply := sdk.ZeroInt()
 | 
						totalSupply := sdk.ZeroInt()
 | 
				
			||||||
	for _, supply := range k.derivatives {
 | 
						for _, supply := range k.derivatives {
 | 
				
			||||||
		totalSupply = totalSupply.Add(supply)
 | 
							totalSupply = totalSupply.Add(supply)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return totalSupply
 | 
						return sdk.NewCoin("ukava", totalSupply), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *fakeLiquidKeeper) GetDerivativeSupply(ctx sdk.Context, denom string) sdk.Int {
 | 
					func (k *fakeLiquidKeeper) GetDerivativeValue(ctx sdk.Context, denom string) (sdk.Coin, error) {
 | 
				
			||||||
	supply, found := k.derivatives[denom]
 | 
						supply, found := k.derivatives[denom]
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
 | 
							return sdk.NewCoin("ukava", sdk.ZeroInt()), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sdk.NewCoin("ukava", supply), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *fakeLiquidKeeper) CollectStakingRewardsByDenom(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						derivativeDenom string,
 | 
				
			||||||
 | 
						destinationModAccount string,
 | 
				
			||||||
 | 
					) (sdk.Coins, error) {
 | 
				
			||||||
 | 
						amt := k.getRewardAmount(ctx, derivativeDenom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sdk.NewCoins(sdk.NewCoin("ukava", amt)), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *fakeLiquidKeeper) getRewardAmount(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						derivativeDenom string,
 | 
				
			||||||
 | 
					) sdk.Int {
 | 
				
			||||||
 | 
						amt, found := k.derivatives[derivativeDenom]
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							// No error
 | 
				
			||||||
		return sdk.ZeroInt()
 | 
							return sdk.ZeroInt()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return supply
 | 
						lastRewardClaim, found := k.lastRewardClaim[derivativeDenom]
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							panic("last reward claim not found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						duration := int64(ctx.BlockTime().Sub(lastRewardClaim).Seconds())
 | 
				
			||||||
 | 
						if duration <= 0 {
 | 
				
			||||||
 | 
							return sdk.ZeroInt()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reward amount just set to 10% of the derivative supply per second
 | 
				
			||||||
 | 
						return amt.QuoRaw(10).MulRaw(duration)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Assorted Testing Data
 | 
					// Assorted Testing Data
 | 
				
			||||||
 | 
				
			|||||||
@ -88,7 +88,7 @@ func (*Accumulator) calculateNewRewards(rewardsPerSecond sdk.DecCoins, totalSour
 | 
				
			|||||||
		// So return an empty increment instead of one full of zeros.
 | 
							// So return an empty increment instead of one full of zeros.
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	increment := newRewardIndexesFromCoins(rewardsPerSecond)
 | 
						increment := NewRewardIndexesFromCoins(rewardsPerSecond)
 | 
				
			||||||
	increment = increment.Mul(sdk.NewDec(durationSeconds)).Quo(totalSourceShares)
 | 
						increment = increment.Mul(sdk.NewDec(durationSeconds)).Quo(totalSourceShares)
 | 
				
			||||||
	return increment
 | 
						return increment
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -109,11 +109,36 @@ func maxTime(t1, t2 time.Time) time.Time {
 | 
				
			|||||||
	return t1
 | 
						return t1
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newRewardIndexesFromCoins is a helper function to initialize a RewardIndexes slice with the values from a Coins slice.
 | 
					// NewRewardIndexesFromCoins is a helper function to initialize a RewardIndexes slice with the values from a Coins slice.
 | 
				
			||||||
func newRewardIndexesFromCoins(coins sdk.DecCoins) RewardIndexes {
 | 
					func NewRewardIndexesFromCoins(coins sdk.DecCoins) RewardIndexes {
 | 
				
			||||||
	var indexes RewardIndexes
 | 
						var indexes RewardIndexes
 | 
				
			||||||
	for _, coin := range coins {
 | 
						for _, coin := range coins {
 | 
				
			||||||
		indexes = append(indexes, NewRewardIndex(coin.Denom, coin.Amount))
 | 
							indexes = append(indexes, NewRewardIndex(coin.Denom, coin.Amount))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return indexes
 | 
						return indexes
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CalculatePerSecondRewards(
 | 
				
			||||||
 | 
						periodStart time.Time,
 | 
				
			||||||
 | 
						periodEnd time.Time,
 | 
				
			||||||
 | 
						periodRewardsPerSecond sdk.DecCoins,
 | 
				
			||||||
 | 
						previousTime, currentTime time.Time,
 | 
				
			||||||
 | 
					) (sdk.DecCoins, time.Time) {
 | 
				
			||||||
 | 
						duration := (&Accumulator{}).getTimeElapsedWithinLimits(
 | 
				
			||||||
 | 
							previousTime,
 | 
				
			||||||
 | 
							currentTime,
 | 
				
			||||||
 | 
							periodStart,
 | 
				
			||||||
 | 
							periodEnd,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						upTo := minTime(periodEnd, currentTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						durationSeconds := int64(math.RoundToEven(duration.Seconds()))
 | 
				
			||||||
 | 
						if durationSeconds <= 0 {
 | 
				
			||||||
 | 
							// If the duration is zero, there will be no increment.
 | 
				
			||||||
 | 
							// So return an empty increment instead of one full of zeros.
 | 
				
			||||||
 | 
							return nil, upTo // TODO
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return periodRewardsPerSecond.MulDec(sdk.NewDec(durationSeconds)), upTo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -74,8 +74,13 @@ type EarnKeeper interface {
 | 
				
			|||||||
// LiquidKeeper defines the required methods needed by this modules keeper
 | 
					// LiquidKeeper defines the required methods needed by this modules keeper
 | 
				
			||||||
type LiquidKeeper interface {
 | 
					type LiquidKeeper interface {
 | 
				
			||||||
	IsDerivativeDenom(ctx sdk.Context, denom string) bool
 | 
						IsDerivativeDenom(ctx sdk.Context, denom string) bool
 | 
				
			||||||
	GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int
 | 
						GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error)
 | 
				
			||||||
	GetDerivativeSupply(ctx sdk.Context, denom string) sdk.Int
 | 
						GetDerivativeValue(ctx sdk.Context, denom string) (sdk.Coin, error)
 | 
				
			||||||
 | 
						CollectStakingRewardsByDenom(
 | 
				
			||||||
 | 
							ctx sdk.Context,
 | 
				
			||||||
 | 
							derivativeDenom string,
 | 
				
			||||||
 | 
							destinationModAccount string,
 | 
				
			||||||
 | 
						) (sdk.Coins, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AccountKeeper expected interface for the account keeper (noalias)
 | 
					// AccountKeeper expected interface for the account keeper (noalias)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										51
									
								
								x/liquid/keeper/claim.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								x/liquid/keeper/claim.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					package keeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/liquid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k Keeper) CollectStakingRewards(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						validator sdk.ValAddress,
 | 
				
			||||||
 | 
						destinationModAccount string,
 | 
				
			||||||
 | 
					) (sdk.Coins, error) {
 | 
				
			||||||
 | 
						macc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure withdraw address is as expected
 | 
				
			||||||
 | 
						withdrawAddr := k.distributionKeeper.GetDelegatorWithdrawAddr(ctx, macc.GetAddress())
 | 
				
			||||||
 | 
						if !withdrawAddr.Equals(macc.GetAddress()) {
 | 
				
			||||||
 | 
							panic(fmt.Sprintf(
 | 
				
			||||||
 | 
								"unexpected withdraw address for liquid staking module account, expected %s, got %s",
 | 
				
			||||||
 | 
								macc.GetAddress(), withdrawAddr,
 | 
				
			||||||
 | 
							))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rewards, err := k.distributionKeeper.WithdrawDelegationRewards(ctx, macc.GetAddress(), validator)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleAccountName, destinationModAccount, rewards)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rewards, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k Keeper) CollectStakingRewardsByDenom(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						derivativeDenom string,
 | 
				
			||||||
 | 
						destinationModAccount string,
 | 
				
			||||||
 | 
					) (sdk.Coins, error) {
 | 
				
			||||||
 | 
						valAddr, err := types.ParseLiquidStakingTokenDenom(derivativeDenom)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return k.CollectStakingRewards(ctx, valAddr, destinationModAccount)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								x/liquid/keeper/claim_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								x/liquid/keeper/claim_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					package keeper_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						"github.com/cosmos/cosmos-sdk/x/staking"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/app"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/liquid/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *KeeperTestSuite) TestCollectStakingRewards() {
 | 
				
			||||||
 | 
						_, addrs := app.GeneratePrivKeyAddressPairs(5)
 | 
				
			||||||
 | 
						valAccAddr1, delegator := addrs[0], addrs[1]
 | 
				
			||||||
 | 
						valAddr1 := sdk.ValAddress(valAccAddr1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initialBalance := i(1e9)
 | 
				
			||||||
 | 
						delegateAmount := i(100e6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.NoError(suite.App.FundModuleAccount(
 | 
				
			||||||
 | 
							suite.Ctx,
 | 
				
			||||||
 | 
							distrtypes.ModuleName,
 | 
				
			||||||
 | 
							sdk.NewCoins(
 | 
				
			||||||
 | 
								sdk.NewCoin("ukava", initialBalance),
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
						))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateAccountWithAddress(valAccAddr1, suite.NewBondCoins(initialBalance))
 | 
				
			||||||
 | 
						suite.CreateAccountWithAddress(delegator, suite.NewBondCoins(initialBalance))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateNewUnbondedValidator(valAddr1, initialBalance)
 | 
				
			||||||
 | 
						suite.CreateDelegation(valAddr1, delegator, delegateAmount)
 | 
				
			||||||
 | 
						staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Transfers delegation to module account
 | 
				
			||||||
 | 
						_, err := suite.Keeper.MintDerivative(suite.Ctx, delegator, valAddr1, suite.NewBondCoin(delegateAmount))
 | 
				
			||||||
 | 
						suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr1)
 | 
				
			||||||
 | 
						suite.Require().True(found)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Ctx = suite.Ctx.WithBlockHeight(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						distrKeeper := suite.App.GetDistrKeeper()
 | 
				
			||||||
 | 
						stakingKeeper := suite.App.GetStakingKeeper()
 | 
				
			||||||
 | 
						accKeeper := suite.App.GetAccountKeeper()
 | 
				
			||||||
 | 
						liquidMacc := accKeeper.GetModuleAccount(suite.Ctx, types.ModuleAccountName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add rewards
 | 
				
			||||||
 | 
						rewardCoins := sdk.NewDecCoins(sdk.NewDecCoin("ukava", sdk.NewInt(500e6)))
 | 
				
			||||||
 | 
						distrKeeper.AllocateTokensToValidator(suite.Ctx, validator, rewardCoins)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delegation, found := stakingKeeper.GetDelegation(suite.Ctx, liquidMacc.GetAddress(), valAddr1)
 | 
				
			||||||
 | 
						suite.Require().True(found)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get amount of rewards
 | 
				
			||||||
 | 
						endingPeriod := distrKeeper.IncrementValidatorPeriod(suite.Ctx, validator)
 | 
				
			||||||
 | 
						delegationRewards := distrKeeper.CalculateDelegationRewards(suite.Ctx, validator, delegation, endingPeriod)
 | 
				
			||||||
 | 
						truncatedRewards, _ := delegationRewards.TruncateDecimal()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("collect staking rewards", func() {
 | 
				
			||||||
 | 
							// Collect rewards
 | 
				
			||||||
 | 
							derivativeDenom := suite.Keeper.GetLiquidStakingTokenDenom(valAddr1)
 | 
				
			||||||
 | 
							rewards, err := suite.Keeper.CollectStakingRewardsByDenom(suite.Ctx, derivativeDenom, types.ModuleName)
 | 
				
			||||||
 | 
							suite.Require().NoError(err)
 | 
				
			||||||
 | 
							suite.Require().Equal(truncatedRewards, rewards)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							suite.True(rewards.AmountOf("ukava").IsPositive())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check balances
 | 
				
			||||||
 | 
							suite.AccountBalanceEqual(liquidMacc.GetAddress(), rewards)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("collect staking rewards with non-validator", func() {
 | 
				
			||||||
 | 
							// acc2 not a validator
 | 
				
			||||||
 | 
							derivativeDenom := suite.Keeper.GetLiquidStakingTokenDenom(sdk.ValAddress(addrs[2]))
 | 
				
			||||||
 | 
							_, err := suite.Keeper.CollectStakingRewardsByDenom(suite.Ctx, derivativeDenom, types.ModuleName)
 | 
				
			||||||
 | 
							suite.Require().Error(err)
 | 
				
			||||||
 | 
							suite.Require().Equal("no validator distribution info", err.Error())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("collect staking rewards with invalid denom", func() {
 | 
				
			||||||
 | 
							derivativeDenom := "bkava"
 | 
				
			||||||
 | 
							_, err := suite.Keeper.CollectStakingRewardsByDenom(suite.Ctx, derivativeDenom, types.ModuleName)
 | 
				
			||||||
 | 
							suite.Require().Error(err)
 | 
				
			||||||
 | 
							suite.Require().Equal("cannot parse denom bkava", err.Error())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -136,6 +136,28 @@ func (k Keeper) GetStakedTokensForDerivatives(ctx sdk.Context, coins sdk.Coins)
 | 
				
			|||||||
	return totalCoin, nil
 | 
						return totalCoin, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTotalDerivativeValue returns the total sum value of all derivative coins
 | 
				
			||||||
 | 
					// for all validators denominated by the bond token (ukava).
 | 
				
			||||||
 | 
					func (k Keeper) GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error) {
 | 
				
			||||||
 | 
						bkavaCoins := sdk.NewCoins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.bankKeeper.IterateTotalSupply(ctx, func(c sdk.Coin) bool {
 | 
				
			||||||
 | 
							if k.IsDerivativeDenom(ctx, c.Denom) {
 | 
				
			||||||
 | 
								bkavaCoins = bkavaCoins.Add(c)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return k.GetStakedTokensForDerivatives(ctx, bkavaCoins)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDerivativeValue returns the total underlying value of the provided
 | 
				
			||||||
 | 
					// derivative denominated by the bond token (ukava).
 | 
				
			||||||
 | 
					func (k Keeper) GetDerivativeValue(ctx sdk.Context, denom string) (sdk.Coin, error) {
 | 
				
			||||||
 | 
						return k.GetStakedTokensForDerivatives(ctx, sdk.NewCoins(k.bankKeeper.GetSupply(ctx, denom)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k Keeper) mintCoins(ctx sdk.Context, receiver sdk.AccAddress, amount sdk.Coins) error {
 | 
					func (k Keeper) mintCoins(ctx sdk.Context, receiver sdk.AccAddress, amount sdk.Coins) error {
 | 
				
			||||||
	if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, amount); err != nil {
 | 
						if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, amount); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
 | 
				
			|||||||
@ -470,6 +470,61 @@ func (suite *KeeperTestSuite) TestGetStakedTokensForDerivatives() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *KeeperTestSuite) TestGetDerivativeValue() {
 | 
				
			||||||
 | 
						_, addrs := app.GeneratePrivKeyAddressPairs(5)
 | 
				
			||||||
 | 
						valAccAddr1, delegator, valAccAddr2 := addrs[0], addrs[1], addrs[2]
 | 
				
			||||||
 | 
						valAddr1 := sdk.ValAddress(valAccAddr1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valAddr2 := sdk.ValAddress(valAccAddr2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initialBalance := i(1e9)
 | 
				
			||||||
 | 
						vestedBalance := i(500e6)
 | 
				
			||||||
 | 
						delegateAmount := i(100e6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateAccountWithAddress(valAccAddr1, suite.NewBondCoins(initialBalance))
 | 
				
			||||||
 | 
						suite.CreateVestingAccountWithAddress(delegator, suite.NewBondCoins(initialBalance), suite.NewBondCoins(vestedBalance))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateNewUnbondedValidator(valAddr1, initialBalance)
 | 
				
			||||||
 | 
						suite.CreateDelegation(valAddr1, delegator, delegateAmount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateAccountWithAddress(valAccAddr2, suite.NewBondCoins(initialBalance))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.CreateNewUnbondedValidator(valAddr2, initialBalance)
 | 
				
			||||||
 | 
						suite.CreateDelegation(valAddr2, delegator, delegateAmount)
 | 
				
			||||||
 | 
						staking.EndBlocker(suite.Ctx, suite.StakingKeeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := suite.Keeper.MintDerivative(suite.Ctx, delegator, valAddr1, suite.NewBondCoin(delegateAmount))
 | 
				
			||||||
 | 
						suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = suite.Keeper.MintDerivative(suite.Ctx, delegator, valAddr2, suite.NewBondCoin(delegateAmount))
 | 
				
			||||||
 | 
						suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.SlashValidator(valAddr2, d("0.05"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("total value", func() {
 | 
				
			||||||
 | 
							totalValue, err := suite.Keeper.GetTotalDerivativeValue(suite.Ctx)
 | 
				
			||||||
 | 
							suite.Require().NoError(err)
 | 
				
			||||||
 | 
							suite.Require().Equal(
 | 
				
			||||||
 | 
								// delegateAmount + (delegateAmount * 95%)
 | 
				
			||||||
 | 
								delegateAmount.Add(delegateAmount.MulRaw(95).QuoRaw(100)),
 | 
				
			||||||
 | 
								totalValue.Amount,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("1:1 derivative value", func() {
 | 
				
			||||||
 | 
							derivativeValue, err := suite.Keeper.GetDerivativeValue(suite.Ctx, suite.Keeper.GetLiquidStakingTokenDenom(valAddr1))
 | 
				
			||||||
 | 
							suite.Require().NoError(err)
 | 
				
			||||||
 | 
							suite.Require().Equal(suite.NewBondCoin(delegateAmount), derivativeValue)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("slashed derivative value", func() {
 | 
				
			||||||
 | 
							derivativeValue, err := suite.Keeper.GetDerivativeValue(suite.Ctx, suite.Keeper.GetLiquidStakingTokenDenom(valAddr2))
 | 
				
			||||||
 | 
							suite.Require().NoError(err)
 | 
				
			||||||
 | 
							// delegateAmount * 95%
 | 
				
			||||||
 | 
							suite.Require().Equal(delegateAmount.MulRaw(95).QuoRaw(100), derivativeValue.Amount)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *KeeperTestSuite) TestDerivativeFromTokens() {
 | 
					func (suite *KeeperTestSuite) TestDerivativeFromTokens() {
 | 
				
			||||||
	_, addrs := app.GeneratePrivKeyAddressPairs(1)
 | 
						_, addrs := app.GeneratePrivKeyAddressPairs(1)
 | 
				
			||||||
	valAccAddr := addrs[0]
 | 
						valAccAddr := addrs[0]
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ type Keeper struct {
 | 
				
			|||||||
	accountKeeper      types.AccountKeeper
 | 
						accountKeeper      types.AccountKeeper
 | 
				
			||||||
	bankKeeper         types.BankKeeper
 | 
						bankKeeper         types.BankKeeper
 | 
				
			||||||
	stakingKeeper      types.StakingKeeper
 | 
						stakingKeeper      types.StakingKeeper
 | 
				
			||||||
 | 
						distributionKeeper types.DistributionKeeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	derivativeDenom string
 | 
						derivativeDenom string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -24,7 +25,7 @@ type Keeper struct {
 | 
				
			|||||||
// NewKeeper returns a new keeper for the liquid module.
 | 
					// NewKeeper returns a new keeper for the liquid module.
 | 
				
			||||||
func NewKeeper(
 | 
					func NewKeeper(
 | 
				
			||||||
	cdc codec.Codec,
 | 
						cdc codec.Codec,
 | 
				
			||||||
	ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper,
 | 
						ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.DistributionKeeper,
 | 
				
			||||||
	derivativeDenom string,
 | 
						derivativeDenom string,
 | 
				
			||||||
) Keeper {
 | 
					) Keeper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,6 +34,7 @@ func NewKeeper(
 | 
				
			|||||||
		accountKeeper:      ak,
 | 
							accountKeeper:      ak,
 | 
				
			||||||
		bankKeeper:         bk,
 | 
							bankKeeper:         bk,
 | 
				
			||||||
		stakingKeeper:      sk,
 | 
							stakingKeeper:      sk,
 | 
				
			||||||
 | 
							distributionKeeper: dk,
 | 
				
			||||||
		derivativeDenom:    derivativeDenom,
 | 
							derivativeDenom:    derivativeDenom,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -40,10 +42,10 @@ func NewKeeper(
 | 
				
			|||||||
// NewDefaultKeeper returns a new keeper for the liquid module with default values.
 | 
					// NewDefaultKeeper returns a new keeper for the liquid module with default values.
 | 
				
			||||||
func NewDefaultKeeper(
 | 
					func NewDefaultKeeper(
 | 
				
			||||||
	cdc codec.Codec,
 | 
						cdc codec.Codec,
 | 
				
			||||||
	ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper,
 | 
						ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.DistributionKeeper,
 | 
				
			||||||
) Keeper {
 | 
					) Keeper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return NewKeeper(cdc, ak, bk, sk, types.DefaultDerivativeDenom)
 | 
						return NewKeeper(cdc, ak, bk, sk, dk, types.DefaultDerivativeDenom)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Logger returns a module-specific logger.
 | 
					// Logger returns a module-specific logger.
 | 
				
			||||||
 | 
				
			|||||||
@ -94,7 +94,7 @@ func (suite *KeeperTestSuite) AddCoinsToModule(module string, amount sdk.Coins)
 | 
				
			|||||||
// AccountBalanceEqual checks if an account has the specified coins.
 | 
					// AccountBalanceEqual checks if an account has the specified coins.
 | 
				
			||||||
func (suite *KeeperTestSuite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) {
 | 
					func (suite *KeeperTestSuite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) {
 | 
				
			||||||
	balance := suite.BankKeeper.GetAllBalances(suite.Ctx, addr)
 | 
						balance := suite.BankKeeper.GetAllBalances(suite.Ctx, addr)
 | 
				
			||||||
	suite.Equalf(coins, balance, "expected account balance to equal coins %s, but got %s", coins, balance)
 | 
						suite.Truef(coins.IsEqual(balance), "expected account balance to equal coins %s, but got %s", coins, balance)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *KeeperTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error {
 | 
					func (suite *KeeperTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,9 @@ type BankKeeper interface {
 | 
				
			|||||||
	MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
 | 
						MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
 | 
				
			||||||
	BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
 | 
						BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
 | 
				
			||||||
	UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
 | 
						UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool)
 | 
				
			||||||
 | 
						GetSupply(ctx sdk.Context, denom string) sdk.Coin
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AccountKeeper defines the expected keeper interface for interacting with account
 | 
					// AccountKeeper defines the expected keeper interface for interacting with account
 | 
				
			||||||
@ -45,3 +48,8 @@ type StakingKeeper interface {
 | 
				
			|||||||
		ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec,
 | 
							ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec,
 | 
				
			||||||
	) (amount sdk.Int, err error)
 | 
						) (amount sdk.Int, err error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DistributionKeeper interface {
 | 
				
			||||||
 | 
						GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress
 | 
				
			||||||
 | 
						WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user