mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 08:45:19 +00:00
4c1524d7bc
* 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
147 lines
4.6 KiB
Go
147 lines
4.6 KiB
Go
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
|
|
const (
|
|
SecondsPerYear = uint64(31536000)
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// 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 (m Minter) CalculateInflationRate(secondsPassed uint64) (sdk.Dec, error) {
|
|
perSecondInterestRate, err := apyToSpy(m.rate.Add(sdk.OneDec()))
|
|
if err != nil {
|
|
return sdk.ZeroDec(), err
|
|
}
|
|
rate := perSecondInterestRate.Power(secondsPassed)
|
|
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) {
|
|
// Note: any APY greater than 176.5 will cause an out-of-bounds error
|
|
root, err := apy.ApproxRoot(SecondsPerYear)
|
|
if err != nil {
|
|
return sdk.ZeroDec(), err
|
|
}
|
|
return root, nil
|
|
}
|