Skip to content

Commit

Permalink
feat(mint): implement inflation calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
bdeneux committed Nov 30, 2022
1 parent 3c198f7 commit 42bfa4c
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 112 deletions.
22 changes: 13 additions & 9 deletions x/mint/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@ import (
)

// BeginBlocker mints new tokens for the previous block.
func BeginBlocker(ctx sdk.Context, k keeper.Keeper, ic types.InflationCalculationFn) {
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

// fetch stored minter & params
minter := k.GetMinter(ctx)
params := k.GetParams(ctx)

// recalculate inflation rate
totalStakingSupply := k.StakingTokenSupply(ctx)
bondedRatio := k.BondedRatio(ctx)
minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
k.SetMinter(ctx, minter)
totalSupply := k.TokenSupply(ctx, params.MintDenom)
targetSupply := minter.TargetSupply

// If we have reached the end of the year by reaching the targeted supply for the year
// We need to re-calculate the next inflation for the next year.
if totalSupply.GTE(targetSupply) {
minter.Inflation = minter.NextInflation()
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalSupply)
minter.TargetSupply = totalSupply.Add(minter.AnnualProvisions.TruncateInt())
k.SetMinter(ctx, minter)
}

// mint coins, update supply
mintedCoin := minter.BlockProvision(params)
mintedCoin := minter.BlockProvision(params, totalSupply)
mintedCoins := sdk.NewCoins(mintedCoin)

err := k.MintCoins(ctx, mintedCoins)
Expand All @@ -46,7 +51,6 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, ic types.InflationCalculatio
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeMint,
sdk.NewAttribute(types.AttributeKeyBondedRatio, bondedRatio.String()),
sdk.NewAttribute(types.AttributeKeyInflation, minter.Inflation.String()),
sdk.NewAttribute(types.AttributeKeyAnnualProvisions, minter.AnnualProvisions.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, mintedCoin.Amount.String()),
Expand Down
8 changes: 7 additions & 1 deletion x/mint/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/okp4/okp4d/x/mint/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/okp4/okp4d/x/mint/types"
)

// Keeper of the mint store
Expand Down Expand Up @@ -82,6 +82,12 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}

// TokenSupply implements an alias call to the underlying bank keeper's
// TokenSupply to be used in BeginBlocker.
func (k Keeper) TokenSupply(ctx sdk.Context, denom string) math.Int {
return k.bankKeeper.GetSupply(ctx, denom).Amount
}

// StakingTokenSupply implements an alias call to the underlying staking keeper's
// StakingTokenSupply to be used in BeginBlocker.
func (k Keeper) StakingTokenSupply(ctx sdk.Context) math.Int {
Expand Down
21 changes: 6 additions & 15 deletions x/mint/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,14 @@ type AppModule struct {

keeper keeper.Keeper
authKeeper types.AccountKeeper

// inflationCalculator is used to calculate the inflation rate during BeginBlock.
// If inflationCalculator is nil, the default inflation calculation logic is used.
inflationCalculator types.InflationCalculationFn
}

// NewAppModule creates a new AppModule object. If the InflationCalculationFn
// argument is nil, then the SDK's default inflation function will be used.
func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, ak types.AccountKeeper, ic types.InflationCalculationFn) AppModule {
if ic == nil {
ic = types.DefaultInflationCalculationFn
}
// NewAppModule creates a new AppModule object.
func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, ak types.AccountKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{cdc: cdc},
keeper: keeper,
authKeeper: ak,
inflationCalculator: ic,
AppModuleBasic: AppModuleBasic{cdc: cdc},
keeper: keeper,
authKeeper: ak,
}
}

Expand Down Expand Up @@ -152,7 +143,7 @@ func (AppModule) ConsensusVersion() uint64 { return 1 }

// BeginBlock returns the begin blocker for the mint module.
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper, am.inflationCalculator)
BeginBlocker(ctx, am.keeper)
}

// AppModuleSimulation functions
Expand Down
5 changes: 3 additions & 2 deletions x/mint/simulation/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"math/rand"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/okp4/okp4d/x/mint/types"
Expand Down Expand Up @@ -82,9 +83,9 @@ func RandomizedGenState(simState *module.SimulationState) {

mintDenom := sdk.DefaultBondDenom
blocksPerYear := uint64(60 * 60 * 8766 / 5)
params := types.NewParams(mintDenom, inflationRateChange, inflationMax, inflationMin, goalBonded, blocksPerYear)
params := types.NewParams(mintDenom, blocksPerYear)

mintGenesis := types.NewGenesisState(types.InitialMinter(inflation), params)
mintGenesis := types.NewGenesisState(types.InitialMinter(inflation, sdk.NewDecWithPrec(20, 2), math.NewInt(230)), params)

bz, err := json.MarshalIndent(&mintGenesis, "", " ")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions x/mint/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type AccountKeeper interface {
// BankKeeper defines the contract needed to be fulfilled for banking and supply
// dependencies.
type BankKeeper interface {
GetSupply(ctx sdk.Context, denom string) sdk.Coin
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error
Expand Down
16 changes: 0 additions & 16 deletions x/mint/types/genesis.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// InflationCalculationFn defines the function required to calculate inflation rate during
// BeginBlock. It receives the minter and params stored in the keeper, along with the current
// bondedRatio and returns the newly calculated inflation rate.
// It can be used to specify a custom inflation calculation logic, instead of relying on the
// default logic provided by the sdk.
type InflationCalculationFn func(ctx sdk.Context, minter Minter, params Params, bondedRatio sdk.Dec) sdk.Dec

// DefaultInflationCalculationFn is the default function used to calculate inflation.
func DefaultInflationCalculationFn(_ sdk.Context, minter Minter, params Params, bondedRatio sdk.Dec) sdk.Dec {
return minter.NextInflationRate(params, bondedRatio)
}

// NewGenesisState creates a new GenesisState object
func NewGenesisState(minter Minter, params Params) *GenesisState {
return &GenesisState{
Expand Down
61 changes: 27 additions & 34 deletions x/mint/types/minter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,34 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// NewMinter returns a new Minter object with the given inflation and annual
// provisions values.
func NewMinter(inflation, annualProvisions sdk.Dec) Minter {
// NewMinter returns a new Minter object with the given inflation, annual
// provisions values and annual reduction factor.
func NewMinter(inflation, annualProvisions, annualReductionFactor sdk.Dec, targetSupply math.Int) Minter {
return Minter{
Inflation: inflation,
AnnualProvisions: annualProvisions,
Inflation: inflation,
AnnualProvisions: annualProvisions,
AnnualReductionFactor: annualReductionFactor,
TargetSupply: targetSupply,
}
}

// InitialMinter returns an initial Minter object with a given inflation value.
func InitialMinter(inflation sdk.Dec) Minter {
// InitialMinter returns an initial Minter object with a given inflation value and annual reduction factor.
func InitialMinter(inflation, annualReductionFactor sdk.Dec, targetSupply math.Int) Minter {
return NewMinter(
inflation,
sdk.NewDec(0),
annualReductionFactor,
targetSupply,
)
}

// DefaultInitialMinter returns a default initial Minter object for a new chain
// which uses an inflation rate of 13%.
// which uses an inflation rate of 15%.
func DefaultInitialMinter() Minter {
return InitialMinter(
sdk.NewDecWithPrec(13, 2),
sdk.NewDecWithPrec(15, 2),
sdk.NewDecWithPrec(20, 2),
math.NewInt(230000000000000),
)
}

Expand All @@ -41,30 +47,10 @@ func ValidateMinter(minter Minter) error {
return nil
}

// NextInflationRate returns the new inflation rate for the next hour.
func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) sdk.Dec {
// The target annual inflation rate is recalculated for each previsions cycle. The
// inflation is also subject to a rate change (positive or negative) depending on
// the distance from the desired ratio (67%). The maximum rate change possible is
// defined to be 13% per year, however the annual inflation is capped as between
// 7% and 20%.

// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := sdk.OneDec().
Sub(bondedRatio.Quo(params.GoalBonded)).
Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(sdk.NewDec(int64(params.BlocksPerYear)))

// adjust the new annual inflation for this next cycle
inflation := m.Inflation.Add(inflationRateChange) // note inflationRateChange may be negative
if inflation.GT(params.InflationMax) {
inflation = params.InflationMax
}
if inflation.LT(params.InflationMin) {
inflation = params.InflationMin
}

return inflation
// NextInflation return the new inflation rate for the next year
// Get the current inflation and multiply by (1 - annual reduction factor).
func (m Minter) NextInflation() sdk.Dec {
return m.Inflation.Mul(sdk.OneDec().Sub(m.AnnualReductionFactor))
}

// NextAnnualProvisions returns the annual provisions based on current total
Expand All @@ -75,7 +61,14 @@ func (m Minter) NextAnnualProvisions(_ Params, totalSupply math.Int) sdk.Dec {

// BlockProvision returns the provisions for a block based on the annual
// provisions rate.
func (m Minter) BlockProvision(params Params) sdk.Coin {
func (m Minter) BlockProvision(params Params, totalSupply math.Int) sdk.Coin {
provisionAmt := m.AnnualProvisions.QuoInt(sdk.NewInt(int64(params.BlocksPerYear)))

// Fixe rounding by limiting to the target supply at the end of the year block.
futureSupply := totalSupply.Add(provisionAmt.TruncateInt())
if futureSupply.GT(m.TargetSupply) {
return sdk.NewCoin(params.MintDenom, m.TargetSupply.Sub(totalSupply))
}

return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt())
}
40 changes: 5 additions & 35 deletions x/mint/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,19 @@ func ParamKeyTable() paramtypes.KeyTable {
}

func NewParams(
mintDenom string, inflationRateChange, inflationMax, inflationMin, goalBonded sdk.Dec, blocksPerYear uint64,
mintDenom string, blocksPerYear uint64,
) Params {
return Params{
MintDenom: mintDenom,
InflationRateChange: inflationRateChange,
InflationMax: inflationMax,
InflationMin: inflationMin,
GoalBonded: goalBonded,
BlocksPerYear: blocksPerYear,
MintDenom: mintDenom,
BlocksPerYear: blocksPerYear,
}
}

// default minting module parameters
func DefaultParams() Params {
return Params{
MintDenom: sdk.DefaultBondDenom,
InflationRateChange: sdk.NewDecWithPrec(13, 2),
InflationMax: sdk.NewDecWithPrec(20, 2),
InflationMin: sdk.NewDecWithPrec(7, 2),
GoalBonded: sdk.NewDecWithPrec(67, 2),
BlocksPerYear: uint64(60 * 60 * 8766 / 5), // assuming 5 second block times
MintDenom: sdk.DefaultBondDenom,
BlocksPerYear: uint64(60 * 60 * 8766 / 5), // assuming 5 second block times
}
}

Expand All @@ -56,27 +48,9 @@ func (p Params) Validate() error {
if err := validateMintDenom(p.MintDenom); err != nil {
return err
}
if err := validateInflationRateChange(p.InflationRateChange); err != nil {
return err
}
if err := validateInflationMax(p.InflationMax); err != nil {
return err
}
if err := validateInflationMin(p.InflationMin); err != nil {
return err
}
if err := validateGoalBonded(p.GoalBonded); err != nil {
return err
}
if err := validateBlocksPerYear(p.BlocksPerYear); err != nil {
return err
}
if p.InflationMax.LT(p.InflationMin) {
return fmt.Errorf(
"max inflation (%s) must be greater than or equal to min inflation (%s)",
p.InflationMax, p.InflationMin,
)
}

return nil
}
Expand All @@ -91,10 +65,6 @@ func (p Params) String() string {
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
return paramtypes.ParamSetPairs{
paramtypes.NewParamSetPair(KeyMintDenom, &p.MintDenom, validateMintDenom),
paramtypes.NewParamSetPair(KeyInflationRateChange, &p.InflationRateChange, validateInflationRateChange),
paramtypes.NewParamSetPair(KeyInflationMax, &p.InflationMax, validateInflationMax),
paramtypes.NewParamSetPair(KeyInflationMin, &p.InflationMin, validateInflationMin),
paramtypes.NewParamSetPair(KeyGoalBonded, &p.GoalBonded, validateGoalBonded),
paramtypes.NewParamSetPair(KeyBlocksPerYear, &p.BlocksPerYear, validateBlocksPerYear),
}
}
Expand Down

0 comments on commit 42bfa4c

Please sign in to comment.