mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05: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
|
// check that community pool received balance
|
||||||
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, funds)
|
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, funds)
|
||||||
|
suite.Equal(funds, suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||||
// check that sender had balance deducted
|
// check that sender had balance deducted
|
||||||
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, sdk.NewCoins())
|
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, sdk.NewCoins())
|
||||||
})
|
})
|
||||||
@ -51,12 +52,14 @@ func (suite *KeeperTestSuite) TestCommunityPool() {
|
|||||||
|
|
||||||
// community pool has funds deducted
|
// community pool has funds deducted
|
||||||
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, sdk.NewCoins())
|
suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, sdk.NewCoins())
|
||||||
|
suite.Equal(sdk.NewCoins(), suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||||
// receiver receives the funds
|
// receiver receives the funds
|
||||||
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, funds)
|
suite.App.CheckBalance(suite.T(), suite.Ctx, sender, funds)
|
||||||
})
|
})
|
||||||
|
|
||||||
// can't send more than we have!
|
// can't send more than we have!
|
||||||
suite.Run("DistributeFromCommunityPool - insufficient funds", func() {
|
suite.Run("DistributeFromCommunityPool - insufficient funds", func() {
|
||||||
|
suite.Equal(sdk.NewCoins(), suite.Keeper.GetModuleAccountBalance(suite.Ctx))
|
||||||
err := suite.Keeper.DistributeFromCommunityPool(suite.Ctx, sender, funds)
|
err := suite.Keeper.DistributeFromCommunityPool(suite.Ctx, sender, funds)
|
||||||
suite.Require().ErrorContains(err, "insufficient funds")
|
suite.Require().ErrorContains(err, "insufficient funds")
|
||||||
})
|
})
|
||||||
|
@ -4,46 +4,15 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/earn/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
|
// HandleCommunityPoolDepositProposal is a handler for executing a passed community pool deposit proposal
|
||||||
func HandleCommunityPoolDepositProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolDepositProposal) error {
|
func HandleCommunityPoolDepositProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolDepositProposal) error {
|
||||||
// move funds from community pool to the funding account
|
return k.DepositFromModuleAccount(ctx, k.communityPoolMaccName, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||||
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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleCommunityPoolWithdrawProposal is a handler for executing a passed community pool withdraw proposal.
|
// HandleCommunityPoolWithdrawProposal is a handler for executing a passed community pool withdraw proposal.
|
||||||
func HandleCommunityPoolWithdrawProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolWithdrawProposal) error {
|
func HandleCommunityPoolWithdrawProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolWithdrawProposal) error {
|
||||||
// Withdraw to fund module account
|
_, err := k.WithdrawFromModuleAccount(ctx, k.communityPoolMaccName, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
||||||
withdrawAmount, err := k.WithdrawFromModuleAccount(ctx, kavadisttypes.FundModuleAccount, p.Amount, types.STRATEGY_TYPE_SAVINGS)
|
return err
|
||||||
if err != nil {
|
|
||||||
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))
|
depositAmount := sdk.NewCoin("ukava", sdk.NewInt(10000000))
|
||||||
suite.Require().NoError(suite.App.FundModuleAccount(ctx, macc.GetName(), fundAmount))
|
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)
|
suite.CreateVault("ukava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
||||||
deposit := types.NewCommunityPoolDepositProposal("test title",
|
deposit := types.NewCommunityPoolDepositProposal("test title",
|
||||||
"desc", depositAmount)
|
"desc", depositAmount)
|
||||||
|
@ -1,75 +1,13 @@
|
|||||||
package kavamint
|
package kavamint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/kava-labs/kava/x/kavamint/keeper"
|
"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.
|
// BeginBlocker mints & distributes new tokens for the previous block.
|
||||||
func BeginBlocker(ctx sdk.Context, k keeper.KeeperI) {
|
func BeginBlocker(ctx sdk.Context, k keeper.KeeperI) {
|
||||||
params := k.GetParams(ctx)
|
if err := k.AccumulateAndMintInflation(ctx); err != nil {
|
||||||
// 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 {
|
|
||||||
panic(err)
|
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(),
|
expCommunityPoolBalance: sdk.ZeroInt(),
|
||||||
expFeeCollectorBalance: 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",
|
name: "mints community pool inflation and staking rewards",
|
||||||
blockTime: 6,
|
blockTime: 6,
|
||||||
@ -121,8 +130,8 @@ func (suite *abciTestSuite) Test_BeginBlocker_MintsExpectedTokens() {
|
|||||||
expFeeCollectorBalance: sdk.NewInt(121),
|
expFeeCollectorBalance: sdk.NewInt(121),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "handles extra long block time",
|
name: "handles long block time",
|
||||||
blockTime: 60, // like if we're upgrading the network & it takes an hour to get back up
|
blockTime: 60, // a minute
|
||||||
communityPoolInflation: sdk.NewDecWithPrec(50, 2),
|
communityPoolInflation: sdk.NewDecWithPrec(50, 2),
|
||||||
stakingRewardsApy: sdk.NewDecWithPrec(20, 2),
|
stakingRewardsApy: sdk.NewDecWithPrec(20, 2),
|
||||||
bondedRatio: sdk.NewDecWithPrec(35, 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
|
// 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),
|
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 {
|
for _, tc := range testCases {
|
||||||
|
@ -66,8 +66,8 @@ func (mq MintQueryServer) Inflation(
|
|||||||
ctx := sdk.UnwrapSDKContext(c)
|
ctx := sdk.UnwrapSDKContext(c)
|
||||||
|
|
||||||
stakingApy := mq.keeper.GetParams(ctx).StakingRewardsApy
|
stakingApy := mq.keeper.GetParams(ctx).StakingRewardsApy
|
||||||
totalBonded := mq.keeper.TotalBondedTokens(ctx)
|
totalBonded := mq.keeper.totalBondedTokens(ctx)
|
||||||
totalSupply := mq.keeper.TotalSupply(ctx)
|
totalSupply := mq.keeper.totalSupply(ctx)
|
||||||
|
|
||||||
// inflation = staking_apy * total_bonded / total_supply
|
// inflation = staking_apy * total_bonded / total_supply
|
||||||
inflation := stakingApy.MulInt(totalBonded).QuoInt(totalSupply)
|
inflation := stakingApy.MulInt(totalBonded).QuoInt(totalSupply)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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
|
// this is the same value used in the x/hard
|
||||||
@ -9,32 +13,46 @@ const (
|
|||||||
SecondsPerYear = uint64(31536000)
|
SecondsPerYear = uint64(31536000)
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccumulateInflation calculates the number of coins that should be minted to match a yearly `rate`
|
// Minter wraps the logic of a single source of inflation. It calculates the amount of coins to be
|
||||||
// for interest compounded each second of the year over `secondsSinceLastMint` seconds.
|
// minted to match a yearly `rate` of inflating the `basis`.
|
||||||
// `basis` is the base amount of coins that is inflated.
|
type Minter struct {
|
||||||
func (k Keeper) AccumulateInflation(
|
name string // name of the minting. used as event attribute to report how much is minted
|
||||||
ctx sdk.Context,
|
rate sdk.Dec // the yearly apy of the mint. ex. 20% => 0.2
|
||||||
rate sdk.Dec,
|
basis sdk.Int // the base amount of coins that is inflated
|
||||||
basis sdk.Int,
|
destMaccName string // the destination module account name to mint coins to
|
||||||
secondsSinceLastMint float64,
|
mintDenom string // denom of coin this minter is responsible for minting
|
||||||
) (sdk.Coins, error) {
|
}
|
||||||
bondDenom := k.BondDenom(ctx)
|
|
||||||
|
|
||||||
// calculate the rate factor based on apy & seconds passed since last block
|
// NewMinter returns creates a new source of minting inflation
|
||||||
inflationRate, err := CalculateInflationRate(rate, uint64(secondsSinceLastMint))
|
func NewMinter(
|
||||||
if err != nil {
|
name string, rate sdk.Dec, basis sdk.Int, mintDenom string, destMaccName string,
|
||||||
return sdk.NewCoins(), err
|
) Minter {
|
||||||
|
return Minter{
|
||||||
|
name: name,
|
||||||
|
rate: rate,
|
||||||
|
basis: basis,
|
||||||
|
destMaccName: destMaccName,
|
||||||
|
mintDenom: mintDenom,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
amount := inflationRate.MulInt(basis).TruncateInt()
|
// 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.
|
||||||
return sdk.NewCoins(sdk.NewCoin(bondDenom, amount)), nil
|
// `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
|
// CalculateInflationRate converts an APY into the factor corresponding with that APY's accumulation
|
||||||
// over a period of secondsPassed seconds.
|
// over a period of secondsPassed seconds.
|
||||||
func CalculateInflationRate(apy sdk.Dec, secondsPassed uint64) (sdk.Dec, error) {
|
func (m Minter) CalculateInflationRate(secondsPassed uint64) (sdk.Dec, error) {
|
||||||
perSecondInterestRate, err := apyToSpy(apy.Add(sdk.OneDec()))
|
perSecondInterestRate, err := apyToSpy(m.rate.Add(sdk.OneDec()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdk.ZeroDec(), err
|
return sdk.ZeroDec(), err
|
||||||
}
|
}
|
||||||
@ -42,6 +60,80 @@ func CalculateInflationRate(apy sdk.Dec, secondsPassed uint64) (sdk.Dec, error)
|
|||||||
return rate.Sub(sdk.OneDec()), nil
|
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.
|
// 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.
|
// SPY = Per second compounded interest rate is how cosmos mathematically represents APY.
|
||||||
func apyToSpy(apy sdk.Dec) (sdk.Dec, error) {
|
func apyToSpy(apy sdk.Dec) (sdk.Dec, error) {
|
||||||
|
@ -116,7 +116,8 @@ func (suite *InflationTestSuite) TestCalculateInflationFactor() {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
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)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
marginOfError := sdk.ZeroDec()
|
marginOfError := sdk.ZeroDec()
|
||||||
@ -127,11 +128,35 @@ func (suite *InflationTestSuite) TestCalculateInflationFactor() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: nick bring back
|
suite.Run("errors with out-of-bounds when rate is too high", func() {
|
||||||
//suite.Run("errors when rate is too high", func() {
|
// Dec.ApproxRoot will error w/ out-of-bounds when rate is >176.5
|
||||||
// _, err := keeper.CalculateInflationRate(types.MaxMintingRate.Add(sdk.OneDec()), 100)
|
oob := sdk.NewDec(177)
|
||||||
// suite.Error(err)
|
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) {
|
func (suite *InflationTestSuite) requireWithinError(expected, actual, margin sdk.Dec) {
|
||||||
|
@ -12,28 +12,9 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/kavamint/types"
|
"github.com/kava-labs/kava/x/kavamint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// KeeperI is the required keeper interface for x/kavamint's begin blocker
|
||||||
type KeeperI interface {
|
type KeeperI interface {
|
||||||
GetParams(ctx sdk.Context) (params types.Params)
|
AccumulateAndMintInflation(ctx sdk.Context) error
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeper of the kavamint store
|
// Keeper of the kavamint store
|
||||||
@ -93,44 +74,40 @@ func (k Keeper) GetStakingApy(ctx sdk.Context) sdk.Dec {
|
|||||||
return params.StakingRewardsApy
|
return params.StakingRewardsApy
|
||||||
}
|
}
|
||||||
|
|
||||||
// BondDenom implements an alias call to the underlying staking keeper's BondDenom.
|
// bondDenom implements an alias call to the underlying staking keeper's BondDenom.
|
||||||
func (k Keeper) BondDenom(ctx sdk.Context) string {
|
func (k Keeper) bondDenom(ctx sdk.Context) string {
|
||||||
return k.stakingKeeper.BondDenom(ctx)
|
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.
|
// 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)
|
return k.stakingKeeper.TotalBondedTokens(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MintCoins implements an alias call to the underlying supply keeper's
|
// mintCoinsToModule mints teh desired coins to the x/kavamint module account and then
|
||||||
// MintCoins to be used in BeginBlocker.
|
// transfers them to the designated module account.
|
||||||
func (k Keeper) MintCoins(ctx sdk.Context, newCoins sdk.Coins) error {
|
// if `newCoins` is empty or zero, this method is a noop.
|
||||||
if newCoins.Empty() {
|
func (k Keeper) mintCoinsToModule(ctx sdk.Context, newCoins sdk.Coins, destMaccName string) error {
|
||||||
|
if newCoins.IsZero() {
|
||||||
// skip as no coins need to be minted
|
// skip as no coins need to be minted
|
||||||
return nil
|
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
|
// totalSupply 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
|
|
||||||
// GetSupply for the mint denom to be used in calculating cumulative inflation.
|
// GetSupply for the mint denom to be used in calculating cumulative inflation.
|
||||||
func (k Keeper) TotalSupply(ctx sdk.Context) sdk.Int {
|
func (k Keeper) totalSupply(ctx sdk.Context) sdk.Int {
|
||||||
return k.bankKeeper.GetSupply(ctx, k.BondDenom(ctx)).Amount
|
return k.bankKeeper.GetSupply(ctx, k.bondDenom(ctx)).Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) CumulativeInflation(ctx sdk.Context) sdk.Dec {
|
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)
|
totalInflation = totalInflation.Add(params.CommunityPoolInflation)
|
||||||
|
|
||||||
// staking rewards contribution is the apy * bonded_ratio
|
// staking rewards contribution is the apy * bonded_ratio
|
||||||
bondedSupply := k.TotalBondedTokens(ctx)
|
bondedSupply := k.totalBondedTokens(ctx)
|
||||||
totalSupply := k.TotalSupply(ctx)
|
totalSupply := k.totalSupply(ctx)
|
||||||
bondedRatio := sdk.NewDecFromInt(bondedSupply).QuoInt(totalSupply)
|
bondedRatio := sdk.NewDecFromInt(bondedSupply).QuoInt(totalSupply)
|
||||||
inflationFromStakingRewards := params.StakingRewardsApy.Mul(bondedRatio)
|
inflationFromStakingRewards := params.StakingRewardsApy.Mul(bondedRatio)
|
||||||
|
|
||||||
|
@ -51,3 +51,21 @@ func (suite keeperTestSuite) TestPreviousBlockTime_Persistance() {
|
|||||||
keeper.SetPreviousBlockTime(suite.Ctx, newTime)
|
keeper.SetPreviousBlockTime(suite.Ctx, newTime)
|
||||||
suite.Equal(keeper.GetPreviousBlockTime(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
|
```go
|
||||||
// BeginBlocker mints & distributes new tokens for the previous block.
|
// BeginBlocker mints & distributes new tokens for the previous block.
|
||||||
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||||
params := k.GetParams(ctx)
|
if err := k.AccumulateAndMintInflation(ctx); err != nil {
|
||||||
// fetch the last block time from state
|
panic(err)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`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
|
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.Keeper = suite.App.GetKavamintKeeper()
|
||||||
suite.StakingKeeper = suite.App.GetStakingKeeper()
|
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
|
// SetBondedTokenRatio mints the total supply to an account and creates a validator with a self
|
||||||
|
Loading…
Reference in New Issue
Block a user