mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 07:45:18 +00:00
Refactor community & inflation enhancements (#1428)
* refactor begin blocker to single func * remove unused inflation keeper methods * refactor to private keeper methods * add testcase for failed mint due to invalid param * add testcase for GetStakingApy() * check for zero instead of empty * actually test super long block time * skip fund account for earn community proposals * test x/community keeper GetModuleAccountBalance * update x/kavamint begin block spec
This commit is contained in:
parent
dd856bb288
commit
4c1524d7bc
@ -40,6 +40,7 @@ func (suite *KeeperTestSuite) TestCommunityPool() {
|
||||
|
||||
// check that community pool received balance
|
||||
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, funds)
|
||||
suite.Equal(funds, suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||
// check that sender had balance deducted
|
||||
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, sdk.NewCoins())
|
||||
})
|
||||
@ -51,12 +52,14 @@ func (suite *KeeperTestSuite) TestCommunityPool() {
|
||||
|
||||
// community pool has funds deducted
|
||||
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, sdk.NewCoins())
|
||||
suite.Equal(sdk.NewCoins(), suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||
// receiver receives the funds
|
||||
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, funds)
|
||||
})
|
||||
|
||||
// can't send more than we have!
|
||||
suite.Run("DistributeFromCommunityPool - insufficient funds", func() {
|
||||
suite.Equal(sdk.NewCoins(), suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||
err := suite.Keeper.DistributeFromCommunityPool(suite.Ctx, sender, funds)
|
||||
suite.Require().ErrorContains(err, "insufficient funds")
|
||||
})
|
||||
|
@ -4,46 +4,15 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/earn/types"
|
||||
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
|
||||
)
|
||||
|
||||
// HandleCommunityPoolDepositProposal is a handler for executing a passed community pool deposit proposal
|
||||
func HandleCommunityPoolDepositProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolDepositProposal) error {
|
||||
// move funds from community pool to the funding account
|
||||
if err := k.bankKeeper.SendCoinsFromModuleToModule(
|
||||
ctx,
|
||||
k.communityPoolMaccName,
|
||||
kavadisttypes.FundModuleAccount,
|
||||
sdk.NewCoins(p.Amount),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := k.DepositFromModuleAccount(ctx, kavadisttypes.FundModuleAccount, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
return k.DepositFromModuleAccount(ctx, k.communityPoolMaccName, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||
}
|
||||
|
||||
// HandleCommunityPoolWithdrawProposal is a handler for executing a passed community pool withdraw proposal.
|
||||
func HandleCommunityPoolWithdrawProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolWithdrawProposal) error {
|
||||
// Withdraw to fund module account
|
||||
withdrawAmount, err := k.WithdrawFromModuleAccount(ctx, kavadisttypes.FundModuleAccount, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||
if err != nil {
|
||||
_, err := k.WithdrawFromModuleAccount(ctx, k.communityPoolMaccName, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||
return err
|
||||
}
|
||||
|
||||
// Move funds to the community pool manually
|
||||
if err := k.bankKeeper.SendCoinsFromModuleToModule(
|
||||
ctx,
|
||||
kavadisttypes.FundModuleAccount,
|
||||
k.communityPoolMaccName,
|
||||
sdk.NewCoins(withdrawAmount),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ func (suite *proposalTestSuite) TestCommunityWithdrawProposal() {
|
||||
depositAmount := sdk.NewCoin("ukava", sdk.NewInt(10000000))
|
||||
suite.Require().NoError(suite.App.FundModuleAccount(ctx, macc.GetName(), fundAmount))
|
||||
|
||||
// TODO update to STRATEGY_TYPE_SAVINGS once implemented
|
||||
suite.CreateVault("ukava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
||||
deposit := types.NewCommunityPoolDepositProposal("test title",
|
||||
"desc", depositAmount)
|
||||
|
@ -1,75 +1,13 @@
|
||||
package kavamint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/x/kavamint/keeper"
|
||||
"github.com/kava-labs/kava/x/kavamint/types"
|
||||
)
|
||||
|
||||
// BeginBlocker mints & distributes new tokens for the previous block.
|
||||
func BeginBlocker(ctx sdk.Context, k keeper.KeeperI) {
|
||||
params := k.GetParams(ctx)
|
||||
// determine seconds since last mint
|
||||
previousBlockTime := k.GetPreviousBlockTime(ctx)
|
||||
if previousBlockTime.IsZero() {
|
||||
previousBlockTime = ctx.BlockTime()
|
||||
}
|
||||
secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds()
|
||||
|
||||
// calculate totals before any minting is done to prevent new mints affecting the values
|
||||
totalSupply := k.TotalSupply(ctx)
|
||||
totalBonded := k.TotalBondedTokens(ctx)
|
||||
|
||||
// ------------- Staking Rewards -------------
|
||||
stakingRewardCoins, err := k.AccumulateInflation(
|
||||
ctx, params.StakingRewardsApy, totalBonded, secondsPassed,
|
||||
)
|
||||
if err != nil {
|
||||
if err := k.AccumulateAndMintInflation(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// mint staking rewards
|
||||
if err := k.MintCoins(ctx, stakingRewardCoins); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// send staking rewards to auth fee collector for distribution to validators
|
||||
if err := k.AddCollectedFees(ctx, stakingRewardCoins); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// ------------- Community Pool -------------
|
||||
communityPoolInflation, err := k.AccumulateInflation(
|
||||
ctx, params.CommunityPoolInflation, totalSupply, secondsPassed,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// mint community pool inflation
|
||||
if err := k.MintCoins(ctx, communityPoolInflation); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// send inflation coins to the community pool (x/community module account)
|
||||
if err := k.FundCommunityPool(ctx, communityPoolInflation); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// ------------- Bookkeeping -------------
|
||||
// bookkeep the previous block time
|
||||
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeMint,
|
||||
sdk.NewAttribute(types.AttributeKeyTotalSupply, totalSupply.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyTotalBonded, totalBonded.String()),
|
||||
sdk.NewAttribute(types.AttributeSecondsPassed, fmt.Sprintf("%f", secondsPassed)),
|
||||
sdk.NewAttribute(types.AttributeKeyCommunityPoolMint, communityPoolInflation.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyStakingRewardMint, stakingRewardCoins.String()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -103,6 +103,15 @@ func (suite *abciTestSuite) Test_BeginBlocker_MintsExpectedTokens() {
|
||||
expCommunityPoolBalance: sdk.ZeroInt(),
|
||||
expFeeCollectorBalance: sdk.ZeroInt(),
|
||||
},
|
||||
{
|
||||
name: "mints no tokens if zero seconds passed",
|
||||
blockTime: 0,
|
||||
communityPoolInflation: sdk.NewDecWithPrec(50, 2),
|
||||
stakingRewardsApy: sdk.NewDecWithPrec(20, 2),
|
||||
bondedRatio: sdk.NewDecWithPrec(35, 2),
|
||||
expCommunityPoolBalance: sdk.ZeroInt(),
|
||||
expFeeCollectorBalance: sdk.ZeroInt(),
|
||||
},
|
||||
{
|
||||
name: "mints community pool inflation and staking rewards",
|
||||
blockTime: 6,
|
||||
@ -121,8 +130,8 @@ func (suite *abciTestSuite) Test_BeginBlocker_MintsExpectedTokens() {
|
||||
expFeeCollectorBalance: sdk.NewInt(121),
|
||||
},
|
||||
{
|
||||
name: "handles extra long block time",
|
||||
blockTime: 60, // like if we're upgrading the network & it takes an hour to get back up
|
||||
name: "handles long block time",
|
||||
blockTime: 60, // a minute
|
||||
communityPoolInflation: sdk.NewDecWithPrec(50, 2),
|
||||
stakingRewardsApy: sdk.NewDecWithPrec(20, 2),
|
||||
bondedRatio: sdk.NewDecWithPrec(35, 2),
|
||||
@ -131,6 +140,17 @@ func (suite *abciTestSuite) Test_BeginBlocker_MintsExpectedTokens() {
|
||||
// https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.20%2C31536000%5D%5C%2841%29%2C60%5D-1%5C%2841%29*1e10*.35
|
||||
expFeeCollectorBalance: sdk.NewInt(1214),
|
||||
},
|
||||
{
|
||||
name: "handles extra long block time",
|
||||
blockTime: 3 * 3600, // three hours
|
||||
communityPoolInflation: sdk.NewDecWithPrec(50, 2),
|
||||
stakingRewardsApy: sdk.NewDecWithPrec(20, 2),
|
||||
bondedRatio: sdk.NewDecWithPrec(35, 2),
|
||||
// https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.5%2C31536000%5D%5C%2841%29%2C3*3600%5D-1%5C%2841%29*1e10
|
||||
expCommunityPoolBalance: sdk.NewInt(1388675),
|
||||
// https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.20%2C31536000%5D%5C%2841%29%2C3*3600%5D-1%5C%2841%29*1e10*.35
|
||||
expFeeCollectorBalance: sdk.NewInt(218542),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@ -66,8 +66,8 @@ func (mq MintQueryServer) Inflation(
|
||||
ctx := sdk.UnwrapSDKContext(c)
|
||||
|
||||
stakingApy := mq.keeper.GetParams(ctx).StakingRewardsApy
|
||||
totalBonded := mq.keeper.TotalBondedTokens(ctx)
|
||||
totalSupply := mq.keeper.TotalSupply(ctx)
|
||||
totalBonded := mq.keeper.totalBondedTokens(ctx)
|
||||
totalSupply := mq.keeper.totalSupply(ctx)
|
||||
|
||||
// inflation = staking_apy * total_bonded / total_supply
|
||||
inflation := stakingApy.MulInt(totalBonded).QuoInt(totalSupply)
|
||||
|
@ -1,7 +1,11 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/kavamint/types"
|
||||
)
|
||||
|
||||
// this is the same value used in the x/hard
|
||||
@ -9,32 +13,46 @@ const (
|
||||
SecondsPerYear = uint64(31536000)
|
||||
)
|
||||
|
||||
// AccumulateInflation calculates the number of coins that should be minted to match a yearly `rate`
|
||||
// for interest compounded each second of the year over `secondsSinceLastMint` seconds.
|
||||
// `basis` is the base amount of coins that is inflated.
|
||||
func (k Keeper) AccumulateInflation(
|
||||
ctx sdk.Context,
|
||||
rate sdk.Dec,
|
||||
basis sdk.Int,
|
||||
secondsSinceLastMint float64,
|
||||
) (sdk.Coins, error) {
|
||||
bondDenom := k.BondDenom(ctx)
|
||||
// Minter wraps the logic of a single source of inflation. It calculates the amount of coins to be
|
||||
// minted to match a yearly `rate` of inflating the `basis`.
|
||||
type Minter struct {
|
||||
name string // name of the minting. used as event attribute to report how much is minted
|
||||
rate sdk.Dec // the yearly apy of the mint. ex. 20% => 0.2
|
||||
basis sdk.Int // the base amount of coins that is inflated
|
||||
destMaccName string // the destination module account name to mint coins to
|
||||
mintDenom string // denom of coin this minter is responsible for minting
|
||||
}
|
||||
|
||||
// calculate the rate factor based on apy & seconds passed since last block
|
||||
inflationRate, err := CalculateInflationRate(rate, uint64(secondsSinceLastMint))
|
||||
if err != nil {
|
||||
return sdk.NewCoins(), err
|
||||
// NewMinter returns creates a new source of minting inflation
|
||||
func NewMinter(
|
||||
name string, rate sdk.Dec, basis sdk.Int, mintDenom string, destMaccName string,
|
||||
) Minter {
|
||||
return Minter{
|
||||
name: name,
|
||||
rate: rate,
|
||||
basis: basis,
|
||||
destMaccName: destMaccName,
|
||||
mintDenom: mintDenom,
|
||||
}
|
||||
}
|
||||
|
||||
amount := inflationRate.MulInt(basis).TruncateInt()
|
||||
|
||||
return sdk.NewCoins(sdk.NewCoin(bondDenom, amount)), nil
|
||||
// AccumulateInflation calculates the number of coins that should be minted to match a yearly `rate`
|
||||
// for interest compounded each second of the year over `secondsPassed` seconds.
|
||||
// `basis` is the base amount of coins that is inflated.
|
||||
func (m Minter) AccumulateInflation(secondsPassed uint64) (sdk.Coin, error) {
|
||||
// calculate the rate factor based on apy & seconds passed since last block
|
||||
inflationRate, err := m.CalculateInflationRate(secondsPassed)
|
||||
if err != nil {
|
||||
return sdk.Coin{}, err
|
||||
}
|
||||
amount := inflationRate.MulInt(m.basis).TruncateInt()
|
||||
return sdk.NewCoin(m.mintDenom, amount), nil
|
||||
}
|
||||
|
||||
// CalculateInflationRate converts an APY into the factor corresponding with that APY's accumulation
|
||||
// over a period of secondsPassed seconds.
|
||||
func CalculateInflationRate(apy sdk.Dec, secondsPassed uint64) (sdk.Dec, error) {
|
||||
perSecondInterestRate, err := apyToSpy(apy.Add(sdk.OneDec()))
|
||||
func (m Minter) CalculateInflationRate(secondsPassed uint64) (sdk.Dec, error) {
|
||||
perSecondInterestRate, err := apyToSpy(m.rate.Add(sdk.OneDec()))
|
||||
if err != nil {
|
||||
return sdk.ZeroDec(), err
|
||||
}
|
||||
@ -42,6 +60,80 @@ func CalculateInflationRate(apy sdk.Dec, secondsPassed uint64) (sdk.Dec, error)
|
||||
return rate.Sub(sdk.OneDec()), nil
|
||||
}
|
||||
|
||||
// AccumulateAndMintInflation defines the sources of inflation, determines the seconds passed since
|
||||
// the last mint, and then mints each source of inflation to the defined destination.
|
||||
func (k Keeper) AccumulateAndMintInflation(ctx sdk.Context) error {
|
||||
params := k.GetParams(ctx)
|
||||
// determine seconds since last mint
|
||||
previousBlockTime := k.GetPreviousBlockTime(ctx)
|
||||
if previousBlockTime.IsZero() {
|
||||
previousBlockTime = ctx.BlockTime()
|
||||
}
|
||||
secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds()
|
||||
|
||||
// calculate totals before any minting is done to prevent new mints affecting the values
|
||||
totalSupply := k.totalSupply(ctx)
|
||||
totalBonded := k.totalBondedTokens(ctx)
|
||||
|
||||
bondDenom := k.bondDenom(ctx)
|
||||
|
||||
// define minters for each contributing piece of inflation
|
||||
minters := []Minter{
|
||||
// staking rewards
|
||||
NewMinter(
|
||||
types.AttributeKeyStakingRewardMint,
|
||||
params.StakingRewardsApy,
|
||||
totalBonded,
|
||||
bondDenom,
|
||||
k.stakingRewardsFeeCollectorName,
|
||||
),
|
||||
// community pool inflation
|
||||
NewMinter(
|
||||
types.AttributeKeyCommunityPoolMint,
|
||||
params.CommunityPoolInflation,
|
||||
totalSupply,
|
||||
bondDenom,
|
||||
k.communityPoolModuleAccountName,
|
||||
),
|
||||
}
|
||||
|
||||
mintEventAttrs := make([]sdk.Attribute, 0, len(minters))
|
||||
for _, minter := range minters {
|
||||
// calculate amount for time passed
|
||||
amount, err := minter.AccumulateInflation(uint64(secondsPassed))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// mint & transfer coins
|
||||
inflation := sdk.NewCoins(amount)
|
||||
if err := k.mintCoinsToModule(ctx, inflation, minter.destMaccName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add attribute to event
|
||||
mintEventAttrs = append(mintEventAttrs, sdk.NewAttribute(
|
||||
minter.name,
|
||||
inflation.String(),
|
||||
))
|
||||
}
|
||||
|
||||
// ------------- Bookkeeping -------------
|
||||
// bookkeep the previous block time
|
||||
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeMint,
|
||||
sdk.NewAttribute(types.AttributeKeyTotalSupply, totalSupply.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyTotalBonded, totalBonded.String()),
|
||||
sdk.NewAttribute(types.AttributeSecondsPassed, fmt.Sprintf("%f", secondsPassed)),
|
||||
).AppendAttributes(mintEventAttrs...),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// apyToSpy converts the input annual interest rate. For example, 10% apy would be passed as 1.10.
|
||||
// SPY = Per second compounded interest rate is how cosmos mathematically represents APY.
|
||||
func apyToSpy(apy sdk.Dec) (sdk.Dec, error) {
|
||||
|
@ -116,7 +116,8 @@ func (suite *InflationTestSuite) TestCalculateInflationFactor() {
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
actualRate, err := keeper.CalculateInflationRate(tc.apy, tc.secondsPassed)
|
||||
minter := keeper.NewMinter(tc.name, tc.apy, sdk.ZeroInt(), "ukava", "ignored")
|
||||
actualRate, err := minter.CalculateInflationRate(tc.secondsPassed)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
marginOfError := sdk.ZeroDec()
|
||||
@ -127,11 +128,35 @@ func (suite *InflationTestSuite) TestCalculateInflationFactor() {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: nick bring back
|
||||
//suite.Run("errors when rate is too high", func() {
|
||||
// _, err := keeper.CalculateInflationRate(types.MaxMintingRate.Add(sdk.OneDec()), 100)
|
||||
// suite.Error(err)
|
||||
//})
|
||||
suite.Run("errors with out-of-bounds when rate is too high", func() {
|
||||
// Dec.ApproxRoot will error w/ out-of-bounds when rate is >176.5
|
||||
oob := sdk.NewDec(177)
|
||||
minter := keeper.NewMinter(
|
||||
"out-of-bounds-minter",
|
||||
oob,
|
||||
sdk.OneInt(),
|
||||
"uakva",
|
||||
"ignored",
|
||||
)
|
||||
_, err := minter.CalculateInflationRate(100)
|
||||
suite.Error(err)
|
||||
|
||||
// ensure max mint rate is less than out-of-bounds value
|
||||
suite.True(types.MaxMintingRate.LT(oob))
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *InflationTestSuite) Test_AccumulateInflation_FailsWithInvalidRate() {
|
||||
oob := sdk.NewDec(177)
|
||||
minter := keeper.NewMinter(
|
||||
"out-of-bounds-minter",
|
||||
oob,
|
||||
sdk.OneInt(),
|
||||
"uakva",
|
||||
"ignored",
|
||||
)
|
||||
_, err := minter.AccumulateInflation(1)
|
||||
suite.ErrorContains(err, "out of bounds")
|
||||
}
|
||||
|
||||
func (suite *InflationTestSuite) requireWithinError(expected, actual, margin sdk.Dec) {
|
||||
|
@ -12,28 +12,9 @@ import (
|
||||
"github.com/kava-labs/kava/x/kavamint/types"
|
||||
)
|
||||
|
||||
// KeeperI is the required keeper interface for x/kavamint's begin blocker
|
||||
type KeeperI interface {
|
||||
GetParams(ctx sdk.Context) (params types.Params)
|
||||
SetParams(ctx sdk.Context, params types.Params)
|
||||
|
||||
BondDenom(ctx sdk.Context) string
|
||||
TotalSupply(ctx sdk.Context) sdk.Int
|
||||
TotalBondedTokens(ctx sdk.Context) sdk.Int
|
||||
|
||||
MintCoins(ctx sdk.Context, newCoins sdk.Coins) error
|
||||
AddCollectedFees(ctx sdk.Context, fees sdk.Coins) error
|
||||
FundCommunityPool(ctx sdk.Context, funds sdk.Coins) error
|
||||
|
||||
GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time)
|
||||
SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time)
|
||||
|
||||
CumulativeInflation(ctx sdk.Context) sdk.Dec
|
||||
AccumulateInflation(
|
||||
ctx sdk.Context,
|
||||
rate sdk.Dec,
|
||||
basis sdk.Int,
|
||||
secondsSinceLastMint float64,
|
||||
) (sdk.Coins, error)
|
||||
AccumulateAndMintInflation(ctx sdk.Context) error
|
||||
}
|
||||
|
||||
// Keeper of the kavamint store
|
||||
@ -93,44 +74,40 @@ func (k Keeper) GetStakingApy(ctx sdk.Context) sdk.Dec {
|
||||
return params.StakingRewardsApy
|
||||
}
|
||||
|
||||
// BondDenom implements an alias call to the underlying staking keeper's BondDenom.
|
||||
func (k Keeper) BondDenom(ctx sdk.Context) string {
|
||||
// bondDenom implements an alias call to the underlying staking keeper's BondDenom.
|
||||
func (k Keeper) bondDenom(ctx sdk.Context) string {
|
||||
return k.stakingKeeper.BondDenom(ctx)
|
||||
}
|
||||
|
||||
// TotalBondedTokens implements an alias call to the underlying staking keeper's
|
||||
// totalBondedTokens implements an alias call to the underlying staking keeper's
|
||||
// TotalBondedTokens to be used in BeginBlocker.
|
||||
func (k Keeper) TotalBondedTokens(ctx sdk.Context) sdk.Int {
|
||||
func (k Keeper) totalBondedTokens(ctx sdk.Context) sdk.Int {
|
||||
return k.stakingKeeper.TotalBondedTokens(ctx)
|
||||
}
|
||||
|
||||
// MintCoins implements an alias call to the underlying supply keeper's
|
||||
// MintCoins to be used in BeginBlocker.
|
||||
func (k Keeper) MintCoins(ctx sdk.Context, newCoins sdk.Coins) error {
|
||||
if newCoins.Empty() {
|
||||
// mintCoinsToModule mints teh desired coins to the x/kavamint module account and then
|
||||
// transfers them to the designated module account.
|
||||
// if `newCoins` is empty or zero, this method is a noop.
|
||||
func (k Keeper) mintCoinsToModule(ctx sdk.Context, newCoins sdk.Coins, destMaccName string) error {
|
||||
if newCoins.IsZero() {
|
||||
// skip as no coins need to be minted
|
||||
return nil
|
||||
}
|
||||
|
||||
return k.bankKeeper.MintCoins(ctx, types.ModuleName, newCoins)
|
||||
// mint the coins
|
||||
err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, newCoins)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// transfer them to the desired destination module account
|
||||
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleAccountName, destMaccName, newCoins)
|
||||
}
|
||||
|
||||
// AddCollectedFees implements an alias call to the underlying supply keeper's
|
||||
// AddCollectedFees to be used in BeginBlocker.
|
||||
func (k Keeper) AddCollectedFees(ctx sdk.Context, fees sdk.Coins) error {
|
||||
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.stakingRewardsFeeCollectorName, fees)
|
||||
}
|
||||
|
||||
// FundCommunityPool implements an alias call to the underlying supply keeper's
|
||||
// FundCommunityPool to be used in BeginBlocker.
|
||||
func (k Keeper) FundCommunityPool(ctx sdk.Context, funds sdk.Coins) error {
|
||||
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.communityPoolModuleAccountName, funds)
|
||||
}
|
||||
|
||||
// TotalSupply implements an alias call to the underlying supply keeper's
|
||||
// totalSupply implements an alias call to the underlying supply keeper's
|
||||
// GetSupply for the mint denom to be used in calculating cumulative inflation.
|
||||
func (k Keeper) TotalSupply(ctx sdk.Context) sdk.Int {
|
||||
return k.bankKeeper.GetSupply(ctx, k.BondDenom(ctx)).Amount
|
||||
func (k Keeper) totalSupply(ctx sdk.Context) sdk.Int {
|
||||
return k.bankKeeper.GetSupply(ctx, k.bondDenom(ctx)).Amount
|
||||
}
|
||||
|
||||
func (k Keeper) CumulativeInflation(ctx sdk.Context) sdk.Dec {
|
||||
@ -141,8 +118,8 @@ func (k Keeper) CumulativeInflation(ctx sdk.Context) sdk.Dec {
|
||||
totalInflation = totalInflation.Add(params.CommunityPoolInflation)
|
||||
|
||||
// staking rewards contribution is the apy * bonded_ratio
|
||||
bondedSupply := k.TotalBondedTokens(ctx)
|
||||
totalSupply := k.TotalSupply(ctx)
|
||||
bondedSupply := k.totalBondedTokens(ctx)
|
||||
totalSupply := k.totalSupply(ctx)
|
||||
bondedRatio := sdk.NewDecFromInt(bondedSupply).QuoInt(totalSupply)
|
||||
inflationFromStakingRewards := params.StakingRewardsApy.Mul(bondedRatio)
|
||||
|
||||
|
@ -51,3 +51,21 @@ func (suite keeperTestSuite) TestPreviousBlockTime_Persistance() {
|
||||
keeper.SetPreviousBlockTime(suite.Ctx, newTime)
|
||||
suite.Equal(keeper.GetPreviousBlockTime(suite.Ctx), newTime)
|
||||
}
|
||||
|
||||
func (suite keeperTestSuite) Test_GetStakingApy() {
|
||||
suite.SetupTest()
|
||||
|
||||
testCases := []sdk.Dec{
|
||||
sdk.ZeroDec(),
|
||||
sdk.MustNewDecFromStr("0.000005"),
|
||||
sdk.MustNewDecFromStr("0.15"),
|
||||
sdk.OneDec(),
|
||||
types.MaxMintingRate,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
params := types.NewParams(sdk.ZeroDec(), tc)
|
||||
suite.Keeper.SetParams(suite.Ctx, params)
|
||||
suite.Equal(tc, suite.Keeper.GetStakingApy(suite.Ctx))
|
||||
}
|
||||
}
|
||||
|
@ -9,41 +9,13 @@ At the start of each block, new KAVA tokens are minted and distributed
|
||||
```go
|
||||
// BeginBlocker mints & distributes new tokens for the previous block.
|
||||
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||
params := k.GetParams(ctx)
|
||||
// fetch the last block time from state
|
||||
previousBlockTime := k.GetPreviousBlockTime(ctx)
|
||||
secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds()
|
||||
|
||||
// determine totals before any new mints
|
||||
totalSupply := k.TotalSupply(ctx)
|
||||
totalBonded := k.TotalBondedTokens(ctx)
|
||||
|
||||
// ------------- Staking Rewards -------------
|
||||
// determine amount of the bond denom to mint for staking rewards
|
||||
stakingRewardCoins, err := k.AccumulateInflation(
|
||||
ctx, params.StakingRewardsApy, totalBonded, secondsPassed,
|
||||
)
|
||||
// mint the staking rewards
|
||||
k.MintCoins(ctx, stakingRewardCoins)
|
||||
// distribute them to the fee pool for distribution by x/distribution
|
||||
k.AddCollectedFees(ctx, stakingRewardCoins)
|
||||
|
||||
// ------------- Community Pool -------------
|
||||
// determine amount of the bond denom to mint for community pool inflation
|
||||
communityPoolInflation, err := k.AccumulateInflation(
|
||||
ctx, params.CommunityPoolInflation, totalSupply, secondsPassed,
|
||||
)
|
||||
// mint the community pool tokens
|
||||
k.MintCoins(ctx, communityPoolCoins)
|
||||
// send them to the community module account (the community pool)
|
||||
k.AddCommunityPoolFunds(ctx, communityPoolCoins)
|
||||
|
||||
// ------------- Bookkeeping -------------
|
||||
// set block time for next iteration's minting
|
||||
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
if err := k.AccumulateAndMintInflation(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AccumulateInflation` determines the effective rate of the yearly interest rate assuming it is
|
||||
`AccumulateAndMintInflation` defines all sources of inflation from yearly APYs set via the parameters.
|
||||
Those rates are converted to the effective rate of the yearly interest rate assuming it is
|
||||
compounded once per second, for the number of seconds since the previous mint. See concepts for
|
||||
more details.
|
||||
more details on calculations & the defined sources of inflation.
|
||||
|
@ -31,7 +31,7 @@ func (suite *KavamintTestSuite) SetupTest() {
|
||||
suite.Keeper = suite.App.GetKavamintKeeper()
|
||||
suite.StakingKeeper = suite.App.GetStakingKeeper()
|
||||
|
||||
suite.BondDenom = suite.Keeper.BondDenom(suite.Ctx)
|
||||
suite.BondDenom = suite.StakingKeeper.BondDenom(suite.Ctx)
|
||||
}
|
||||
|
||||
// SetBondedTokenRatio mints the total supply to an account and creates a validator with a self
|
||||
|
Loading…
Reference in New Issue
Block a user