Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* x/distribution can now utilize an externally managed community pool. NOTE: this will make the message handlers for FundCommunityPool and CommunityPoolSpend error, as well as the query handler for CommunityPool.
* (client) [#18101](https://github.com/cosmos/cosmos-sdk/pull/18101) Add a `keyring-default-keyname` in `client.toml` for specifying a default key name, and skip the need to use the `--from` flag when signing transactions.
* (x/gov) [#24355](https://github.com/cosmos/cosmos-sdk/pull/24355) Allow users to set a custom CalculateVoteResultsAndVotingPower function to be used in govkeeper.Tally.
* (x/mint) [#24436](https://github.com/cosmos/cosmos-sdk/pull/24436) Allow users to set a custom minting function used in the `x/mint` begin blocker.

### Improvements

Expand Down
1 change: 1 addition & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ func NewSimApp(
app.BankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
// mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(minttypes.DefaultInflationCalculationFn)), custom mintFn can be added here
)

app.ProtocolPoolKeeper = protocolpoolkeeper.NewKeeper(
Expand Down
29 changes: 28 additions & 1 deletion x/mint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ sidebar_position: 1

# `x/mint`

The `x/mint` module handles the regular minting of new tokens in a configurable manner.

## Contents

* [State](#state)
Expand All @@ -25,7 +27,7 @@ sidebar_position: 1

### The Minting Mechanism

The minting mechanism was designed to:
The default minting mechanism was designed to:

* allow for a flexible inflation rate determined by market demand targeting a particular bonded-stake ratio
* effect a balance between market liquidity and staked supply
Expand All @@ -46,6 +48,31 @@ It can be broken down in the following way:
* If the actual percentage of bonded tokens is above the goal %-bonded the inflation rate will
decrease until a minimum value is reached

### Custom Minters

As of Cosmos SDK v0.53.0, developers can set a custom `MintFn` for the module for specialized token minting logic.

The function signature that a `MintFn` must implement is as follows:

```go
// MintFn defines the function that needs to be implemented in order to customize the minting process.
type MintFn func(ctx sdk.Context, k *Keeper) error
```

This can be passed to the `Keeper` upon creation with an additional `Option`:

```go
app.MintKeeper = mintkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[minttypes.StoreKey]),
app.StakingKeeper,
app.AccountKeeper,
app.BankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
// mintkeeper.WithMintFn(CUSTOM_MINT_FN), // custom mintFn can be added here
)
```

## State

Expand Down
61 changes: 2 additions & 59 deletions x/mint/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,9 @@ import (
)

// BeginBlocker mints new tokens for the previous block.
func BeginBlocker(ctx context.Context, k keeper.Keeper, ic types.InflationCalculationFn) error {
func BeginBlocker(ctx context.Context, k keeper.Keeper, _ types.InflationCalculationFn) error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just remove the InflationCacluationFn arg? i cant imagine anyone uses this externally

defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyBeginBlocker)

// fetch stored minter & params
minter, err := k.Minter.Get(ctx)
if err != nil {
return err
}

params, err := k.Params.Get(ctx)
if err != nil {
return err
}

// recalculate inflation rate
totalStakingSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}

bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}

minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
if err = k.Minter.Set(ctx, minter); err != nil {
return err
}

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

err = k.MintCoins(ctx, mintedCoins)
if err != nil {
return err
}

// send the minted coins to the fee collector account
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}

if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx.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()),
),
)

return nil
return k.MintFn(sdkCtx)
}
24 changes: 23 additions & 1 deletion x/mint/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,24 @@ type Keeper struct {
Schema collections.Schema
Params collections.Item[types.Params]
Minter collections.Item[types.Minter]

// mintFn is a function that encompasses all minting logic run in the x/mint begin blocker.
mintFn MintFn
}

type InitOption func(*Keeper)

// WithMintFn sets a custom minting function for the x/mint keeper.
func WithMintFn(mintFn MintFn) InitOption {
return func(k *Keeper) {
k.mintFn = mintFn
}
}

// NewKeeper creates a new mint Keeper instance
// NewKeeper creates a new mint Keeper instance.
//
// The mint keeper is always initialized with the DefaultMintFn but this can be overridden with the
// WithMintFn option.
func NewKeeper(
cdc codec.BinaryCodec,
storeService storetypes.KVStoreService,
Expand All @@ -40,6 +55,7 @@ func NewKeeper(
bk types.BankKeeper,
feeCollectorName string,
authority string,
opts ...InitOption,
) Keeper {
// ensure mint module account is set
if addr := ak.GetModuleAddress(types.ModuleName); addr == nil {
Expand All @@ -56,13 +72,19 @@ func NewKeeper(
authority: authority,
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
Minter: collections.NewItem(sb, types.MinterKey, "minter", codec.CollValue[types.Minter](cdc)),
mintFn: DefaultMintFn(types.DefaultInflationCalculationFn),
}

schema, err := sb.Build()
if err != nil {
panic(err)
}
k.Schema = schema

for _, opt := range opts {
opt(&k)
}

return k
}

Expand Down
80 changes: 80 additions & 0 deletions x/mint/keeper/mint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package keeper

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

// MintFn defines the function that needs to be implemented in order to customize the minting process.
type MintFn func(ctx sdk.Context, k *Keeper) error

// MintFn runs the mintFn of the keeper.
func (k *Keeper) MintFn(ctx sdk.Context) error {
return k.mintFn(ctx, k)
}

// DefaultMintFn returns a default mint function.
// The default MintFn has a requirement on staking as it uses bond to calculate inflation.
func DefaultMintFn(ic types.InflationCalculationFn) MintFn {
return func(ctx sdk.Context, k *Keeper) error {
// fetch stored minter & params
minter, err := k.Minter.Get(ctx)
if err != nil {
return err
}

params, err := k.Params.Get(ctx)
if err != nil {
return err
}

// recalculate inflation rate
totalStakingSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}

bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}

minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
if err = k.Minter.Set(ctx, minter); err != nil {
return err
}

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

err = k.MintCoins(ctx, mintedCoins)
if err != nil {
return err
}

// send the minted coins to the fee collector account
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}

if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}

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()),
),
)

return nil
}
}
Comment on lines +9 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider edge cases for negative minted amounts.

Currently, the code does not guard against the possibility (even if theoretically low) of receiving a negative minted amount from “BlockProvision”. If code external to this function inadvertently computes a negative inflation or block provision, this would silently fail only when minting coins. It may be safer to assert that the minted amount is non-negative before proceeding to mint coins.

Add a check to ensure the minted amount is non-negative, for example:

+ if mintedCoin.Amount.IsNegative() {
+   return fmt.Errorf("cannot mint a negative amount: %s", mintedCoin.Amount.String())
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// MintFn defines the function that needs to be implemented in order to customize the minting process.
type MintFn func(ctx sdk.Context, k *Keeper) error
// MintFn runs the mintFn of the keeper.
func (k *Keeper) MintFn(ctx sdk.Context) error {
return k.mintFn(ctx, k)
}
// DefaultMintFn returns a default mint function.
// The default MintFn has a requirement on staking as it uses bond to calculate inflation.
func DefaultMintFn(ic types.InflationCalculationFn) MintFn {
return func(ctx sdk.Context, k *Keeper) error {
// fetch stored minter & params
minter, err := k.Minter.Get(ctx)
if err != nil {
return err
}
params, err := k.Params.Get(ctx)
if err != nil {
return err
}
// recalculate inflation rate
totalStakingSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}
bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}
minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
if err = k.Minter.Set(ctx, minter); err != nil {
return err
}
// mint coins, update supply
mintedCoin := minter.BlockProvision(params)
mintedCoins := sdk.NewCoins(mintedCoin)
err = k.MintCoins(ctx, mintedCoins)
if err != nil {
return err
}
// send the minted coins to the fee collector account
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}
if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx.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()),
),
)
return nil
}
}
// MintFn defines the function that needs to be implemented in order to customize the minting process.
type MintFn func(ctx sdk.Context, k *Keeper) error
// MintFn runs the mintFn of the keeper.
func (k *Keeper) MintFn(ctx sdk.Context) error {
return k.mintFn(ctx, k)
}
// DefaultMintFn returns a default mint function.
// The default MintFn has a requirement on staking as it uses bond to calculate inflation.
func DefaultMintFn(ic types.InflationCalculationFn) MintFn {
return func(ctx sdk.Context, k *Keeper) error {
// fetch stored minter & params
minter, err := k.Minter.Get(ctx)
if err != nil {
return err
}
params, err := k.Params.Get(ctx)
if err != nil {
return err
}
// recalculate inflation rate
totalStakingSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}
bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}
minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
if err = k.Minter.Set(ctx, minter); err != nil {
return err
}
// mint coins, update supply
mintedCoin := minter.BlockProvision(params)
+ if mintedCoin.Amount.IsNegative() {
+ return fmt.Errorf("cannot mint a negative amount: %s", mintedCoin.Amount.String())
+ }
mintedCoins := sdk.NewCoins(mintedCoin)
err = k.MintCoins(ctx, mintedCoins)
if err != nil {
return err
}
// send the minted coins to the fee collector account
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}
if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx.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()),
),
)
return nil
}
}

Loading
Loading