mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 07:45:18 +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.bankKeeper,
|
||||
&app.stakingKeeper,
|
||||
&app.distrKeeper,
|
||||
)
|
||||
savingsKeeper := savingskeeper.NewKeeper(
|
||||
appCodec,
|
||||
@ -625,8 +626,7 @@ func NewApp(
|
||||
app.stakingKeeper,
|
||||
&swapKeeper,
|
||||
&savingsKeeper,
|
||||
// TODO: Liquid keeper
|
||||
nil,
|
||||
&app.liquidKeeper,
|
||||
&earnKeeper,
|
||||
)
|
||||
app.routerKeeper = routerkeeper.NewKeeper(
|
||||
|
@ -1,6 +1,8 @@
|
||||
package incentive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
@ -29,6 +31,8 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||
k.AccumulateSavingsRewards(ctx, rp)
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
earnClaims, err := executeEarnRewardsQuery(cliCtx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hardClaims) > 0 {
|
||||
if err := cliCtx.PrintObjectLegacy(hardClaims); err != nil {
|
||||
return err
|
||||
@ -184,6 +189,11 @@ func queryRewardsCmd() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(earnClaims) > 0 {
|
||||
if err := cliCtx.PrintObjectLegacy(earnClaims); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
@ -32,6 +32,7 @@ func GetTxCmd() *cobra.Command {
|
||||
getCmdClaimDelegator(),
|
||||
getCmdClaimSwap(),
|
||||
getCmdClaimSavings(),
|
||||
getCmdClaimEarn(),
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
@ -209,3 +210,35 @@ func getCmdClaimSavings() *cobra.Command {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
earntypes "github.com/kava-labs/kava/x/earn/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.
|
||||
// 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" {
|
||||
k.accumulateEarnBkavaRewards(ctx, rewardPeriod)
|
||||
return
|
||||
return k.accumulateEarnBkavaRewards(ctx, rewardPeriod)
|
||||
}
|
||||
|
||||
k.accumulateEarnRewards(
|
||||
@ -27,6 +28,8 @@ func (k Keeper) AccumulateEarnRewards(ctx sdk.Context, rewardPeriod types.MultiR
|
||||
rewardPeriod.End,
|
||||
sdk.NewDecCoinsFromCoins(rewardPeriod.RewardsPerSecond...),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetProportionalRewardsPerSecond(
|
||||
@ -59,16 +62,13 @@ func GetProportionalRewardsPerSecond(
|
||||
|
||||
// accumulateEarnBkavaRewards does the same as AccumulateEarnRewards but for
|
||||
// *all* bkava vaults.
|
||||
func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
|
||||
// TODO: Get staking rewards and distribute
|
||||
|
||||
func (k Keeper) accumulateEarnBkavaRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
|
||||
// All bkava vault denoms
|
||||
bkavaVaultsDenoms := make(map[string]bool)
|
||||
|
||||
// bkava vault denoms from earn records (non-empty vaults)
|
||||
k.earnKeeper.IterateVaultRecords(ctx, func(record earntypes.VaultRecord) (stop bool) {
|
||||
// TODO: Replace with single bkava denom check method from liquid
|
||||
if strings.HasPrefix(record.TotalShares.Denom, "bkava-") {
|
||||
if k.liquidKeeper.IsDerivativeDenom(ctx, record.TotalShares.Denom) {
|
||||
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
|
||||
// that were fully withdrawn.
|
||||
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
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
totalBkavaSupply := k.liquidKeeper.GetTotalDerivativeSupply(ctx)
|
||||
totalBkavaValue, err := k.liquidKeeper.GetTotalDerivativeValue(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := 0
|
||||
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.
|
||||
for _, bkavaDenom := range sortedBkavaVaultsDenoms {
|
||||
k.accumulateEarnRewards(
|
||||
derivativeValue, err := k.liquidKeeper.GetDerivativeValue(ctx, bkavaDenom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.accumulateBkavaEarnRewards(
|
||||
ctx,
|
||||
bkavaDenom,
|
||||
rewardPeriod.Start,
|
||||
rewardPeriod.End,
|
||||
GetProportionalRewardsPerSecond(
|
||||
rewardPeriod,
|
||||
totalBkavaSupply,
|
||||
k.liquidKeeper.GetDerivativeSupply(ctx, bkavaDenom),
|
||||
totalBkavaValue.Amount,
|
||||
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(
|
||||
|
@ -94,13 +94,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
||||
vaultDenom1 := "bkava-meow"
|
||||
vaultDenom2 := "bkava-woof"
|
||||
|
||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||
|
||||
earnKeeper := newFakeEarnKeeper().
|
||||
addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("800000"))).
|
||||
addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("200000")))
|
||||
|
||||
liquidKeeper := newFakeLiquidKeeper().
|
||||
addDerivative(vaultDenom1, i(800000)).
|
||||
addDerivative(vaultDenom2, i(200000))
|
||||
addDerivative(suite.ctx, vaultDenom1, i(800000)).
|
||||
addDerivative(suite.ctx, vaultDenom2, i(200000))
|
||||
|
||||
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)
|
||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
|
||||
suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
|
||||
|
||||
@ -164,7 +166,8 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
||||
},
|
||||
{
|
||||
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)
|
||||
vaultDenom2Supply := i(200000)
|
||||
|
||||
liquidKeeper := newFakeLiquidKeeper().
|
||||
addDerivative(vaultDenom1, vaultDenom1Supply).
|
||||
addDerivative(vaultDenom2, vaultDenom2Supply)
|
||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||
|
||||
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, d("700000"))).
|
||||
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)
|
||||
@ -223,7 +230,7 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
||||
}
|
||||
|
||||
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, vaultDenom2, previousAccrualTime)
|
||||
|
||||
@ -252,7 +259,12 @@ func (suite *AccumulateEarnRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
||||
},
|
||||
{
|
||||
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",
|
||||
RewardFactor: d("7.24"),
|
||||
RewardFactor: d("7.24").
|
||||
Add(vaultDenom2Supply.ToDec().
|
||||
QuoInt64(10).
|
||||
MulInt64(3600).
|
||||
Quo(vault2Shares),
|
||||
),
|
||||
},
|
||||
}
|
||||
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() {
|
||||
@ -350,13 +350,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn
|
||||
vaultDenom1 := "bkava-meow"
|
||||
vaultDenom2 := "bkava-woof"
|
||||
|
||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||
|
||||
earnKeeper := newFakeEarnKeeper().
|
||||
addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
|
||||
addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
|
||||
|
||||
liquidKeeper := newFakeLiquidKeeper().
|
||||
addDerivative(vaultDenom1, i(1000000)).
|
||||
addDerivative(vaultDenom2, i(1000000))
|
||||
addDerivative(suite.ctx, vaultDenom1, i(1000000)).
|
||||
addDerivative(suite.ctx, vaultDenom2, i(1000000))
|
||||
|
||||
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)
|
||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom1, previousAccrualTime)
|
||||
suite.keeper.SetEarnRewardAccrualTime(suite.ctx, vaultDenom2, previousAccrualTime)
|
||||
|
||||
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||
|
||||
period := types.NewMultiRewardPeriod(
|
||||
true,
|
||||
"bkava",
|
||||
@ -585,13 +586,16 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bka
|
||||
vaultDenom1 := "bkava-meow"
|
||||
vaultDenom2 := "bkava-woof"
|
||||
|
||||
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||
|
||||
earnKeeper := newFakeEarnKeeper().
|
||||
addVault(vaultDenom1, earntypes.NewVaultShare(vaultDenom1, d("1000000"))).
|
||||
addVault(vaultDenom2, earntypes.NewVaultShare(vaultDenom2, d("1000000")))
|
||||
|
||||
liquidKeeper := newFakeLiquidKeeper().
|
||||
addDerivative(vaultDenom1, i(1000000)).
|
||||
addDerivative(vaultDenom2, i(1000000))
|
||||
addDerivative(suite.ctx, vaultDenom1, i(1000000)).
|
||||
addDerivative(suite.ctx, vaultDenom2, i(1000000))
|
||||
|
||||
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)),
|
||||
)
|
||||
|
||||
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||
|
||||
suite.keeper.AccumulateEarnRewards(suite.ctx, period)
|
||||
|
||||
// After the first accumulation only the current block time should be stored.
|
||||
@ -632,7 +633,9 @@ func (suite *AccumulateEarnRewardsTests) TestStateAddedWhenStateDoesNotExist_bka
|
||||
},
|
||||
{
|
||||
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.
|
||||
type fakeLiquidKeeper struct {
|
||||
derivatives map[string]sdk.Int
|
||||
lastRewardClaim map[string]time.Time
|
||||
}
|
||||
|
||||
var _ types.LiquidKeeper = newFakeLiquidKeeper()
|
||||
@ -440,11 +441,17 @@ var _ types.LiquidKeeper = newFakeLiquidKeeper()
|
||||
func newFakeLiquidKeeper() *fakeLiquidKeeper {
|
||||
return &fakeLiquidKeeper{
|
||||
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.lastRewardClaim[denom] = ctx.BlockTime()
|
||||
return k
|
||||
}
|
||||
|
||||
@ -460,22 +467,56 @@ func (k *fakeLiquidKeeper) GetAllDerivativeDenoms(ctx sdk.Context) (denoms []str
|
||||
return denoms
|
||||
}
|
||||
|
||||
func (k *fakeLiquidKeeper) GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int {
|
||||
func (k *fakeLiquidKeeper) GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error) {
|
||||
totalSupply := sdk.ZeroInt()
|
||||
for _, supply := range k.derivatives {
|
||||
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]
|
||||
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 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
|
||||
|
@ -88,7 +88,7 @@ func (*Accumulator) calculateNewRewards(rewardsPerSecond sdk.DecCoins, totalSour
|
||||
// So return an empty increment instead of one full of zeros.
|
||||
return nil
|
||||
}
|
||||
increment := newRewardIndexesFromCoins(rewardsPerSecond)
|
||||
increment := NewRewardIndexesFromCoins(rewardsPerSecond)
|
||||
increment = increment.Mul(sdk.NewDec(durationSeconds)).Quo(totalSourceShares)
|
||||
return increment
|
||||
}
|
||||
@ -109,11 +109,36 @@ func maxTime(t1, t2 time.Time) time.Time {
|
||||
return t1
|
||||
}
|
||||
|
||||
// newRewardIndexesFromCoins is a helper function to initialize a RewardIndexes slice with the values from a Coins slice.
|
||||
func newRewardIndexesFromCoins(coins sdk.DecCoins) RewardIndexes {
|
||||
// NewRewardIndexesFromCoins is a helper function to initialize a RewardIndexes slice with the values from a Coins slice.
|
||||
func NewRewardIndexesFromCoins(coins sdk.DecCoins) RewardIndexes {
|
||||
var indexes RewardIndexes
|
||||
for _, coin := range coins {
|
||||
indexes = append(indexes, NewRewardIndex(coin.Denom, coin.Amount))
|
||||
}
|
||||
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
|
||||
type LiquidKeeper interface {
|
||||
IsDerivativeDenom(ctx sdk.Context, denom string) bool
|
||||
GetTotalDerivativeSupply(ctx sdk.Context) sdk.Int
|
||||
GetDerivativeSupply(ctx sdk.Context, denom string) sdk.Int
|
||||
GetTotalDerivativeValue(ctx sdk.Context) (sdk.Coin, error)
|
||||
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)
|
||||
|
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, amount); err != nil {
|
||||
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() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
valAccAddr := addrs[0]
|
||||
|
@ -17,6 +17,7 @@ type Keeper struct {
|
||||
accountKeeper types.AccountKeeper
|
||||
bankKeeper types.BankKeeper
|
||||
stakingKeeper types.StakingKeeper
|
||||
distributionKeeper types.DistributionKeeper
|
||||
|
||||
derivativeDenom string
|
||||
}
|
||||
@ -24,7 +25,7 @@ type Keeper struct {
|
||||
// NewKeeper returns a new keeper for the liquid module.
|
||||
func NewKeeper(
|
||||
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,
|
||||
) Keeper {
|
||||
|
||||
@ -33,6 +34,7 @@ func NewKeeper(
|
||||
accountKeeper: ak,
|
||||
bankKeeper: bk,
|
||||
stakingKeeper: sk,
|
||||
distributionKeeper: dk,
|
||||
derivativeDenom: derivativeDenom,
|
||||
}
|
||||
}
|
||||
@ -40,10 +42,10 @@ func NewKeeper(
|
||||
// NewDefaultKeeper returns a new keeper for the liquid module with default values.
|
||||
func NewDefaultKeeper(
|
||||
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 {
|
||||
|
||||
return NewKeeper(cdc, ak, bk, sk, types.DefaultDerivativeDenom)
|
||||
return NewKeeper(cdc, ak, bk, sk, dk, types.DefaultDerivativeDenom)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (suite *KeeperTestSuite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) {
|
||||
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 {
|
||||
|
@ -17,6 +17,9 @@ type BankKeeper interface {
|
||||
MintCoins(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
|
||||
|
||||
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
|
||||
@ -45,3 +48,8 @@ type StakingKeeper interface {
|
||||
ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec,
|
||||
) (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