feat: custom x/mint minting function (#24436)

Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com>
This commit is contained in:
Alex | Interchain Labs 2025-04-14 11:58:34 -04:00 committed by GitHub
parent 2bca0bf2fa
commit c5cbda08f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 423 additions and 80 deletions

View File

@ -60,6 +60,8 @@ 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.
* The `InflationCalculationFn` argument to `mint.NewAppModule()` is now ignored and must be nil. To set a custom `InflationCalculationFn` on the default minter, use `mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(customInflationFn))`.
* (api) [#24428](https://github.com/cosmos/cosmos-sdk/pull/24428) Add block height to response headers
### Improvements

View File

@ -139,6 +139,62 @@ Required wiring:
- entry in SetGenesisModuleOrder
- entry in SetExportModuleOrder **before `x/bank`**
## Custom Minting Function in `x/mint`
This release introduces the ability to configure a custom mint function in `x/mint`. The minting logic is now abstracted as a `MintFn` with a default implementation that can be overridden.
### Whats New
- **Configurable Mint Function:**
A new `MintFn` abstraction is introduced. By default, the module uses `DefaultMintFn`, but you can supply your own implementation.
- **Deprecated InflationCalculationFn Parameter:**
The `InflationCalculationFn` argument previously provided to `mint.NewAppModule()` is now ignored and must be `nil`. To customize the default minters inflation behavior, wrap your custom function with `mintkeeper.DefaultMintFn` and pass it via the `WithMintFn` option:
```go
mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(customInflationFn))
```
### How to Upgrade
1. **Using the Default Minting Function**
No action is needed if youre happy with the default behavior. Make sure your application wiring initializes the MintKeeper like this:
```go
mintKeeper := mintkeeper.NewKeeper(
appCodec,
storeService,
stakingKeeper,
accountKeeper,
bankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
```
2. **Using a Custom Minting Function**
To use a custom minting function, define it as follows and pass it you your mintKeeper when constructing it:
```go
func myCustomMintFunc(ctx sdk.Context, k *mintkeeper.Keeper) {
// do minting...
}
// ...
mintKeeper := mintkeeper.NewKeeper(
appCodec,
storeService,
stakingKeeper,
accountKeeper,
bankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
mintkeeper.WithMintFn(myCustomMintFunc), // Use custom minting function
)
```
### Misc Changes
#### Testnet's init-files Command

View File

@ -349,6 +349,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(

View File

@ -156,7 +156,7 @@ func NewSimApp(
//
// For providing a custom inflation function for x/mint add here your
// custom function that implements the minttypes.InflationCalculationFn
// custom minting function that implements the mintkeeper.MintFn
// interface.
),
)

View File

@ -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)
@ -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
@ -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

View File

@ -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) error {
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)
}

View File

@ -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
}
// NewKeeper creates a new mint Keeper instance
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.
//
// 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,
@ -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 {
@ -56,6 +72,7 @@ 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()
@ -63,6 +80,11 @@ func NewKeeper(
panic(err)
}
k.Schema = schema
for _, opt := range opts {
opt(&k)
}
return k
}

80
x/mint/keeper/mint.go Normal file
View File

@ -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
}
}

204
x/mint/keeper/mint_test.go Normal file
View File

@ -0,0 +1,204 @@
package keeper_test
import (
"context"
"slices"
"testing"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttestutil "github.com/cosmos/cosmos-sdk/x/mint/testutil"
"github.com/cosmos/cosmos-sdk/x/mint/types"
)
// MintFnTestSuite defines the integration test suite for minting.
type MintFnTestSuite struct {
suite.Suite
mintKeeper keeper.Keeper
ctx sdk.Context
stakingKeeper *minttestutil.MockStakingKeeper
bankKeeper *minttestutil.MockBankKeeper
}
// TestMintFnTestSuite runs the mint test suite.
func TestMintFnTestSuite(t *testing.T) {
suite.Run(t, new(MintFnTestSuite))
}
// SetupTest sets up the context, KV store, and mocks.
func (s *MintFnTestSuite) SetupTest() {
encCfg := moduletestutil.MakeTestEncodingConfig(mint.AppModuleBasic{})
key := storetypes.NewKVStoreKey(types.StoreKey)
storeService := runtime.NewKVStoreService(key)
testCtx := testutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test"))
s.ctx = testCtx.Ctx
ctrl := gomock.NewController(s.T())
accountKeeper := minttestutil.NewMockAccountKeeper(ctrl)
s.bankKeeper = minttestutil.NewMockBankKeeper(ctrl)
s.stakingKeeper = minttestutil.NewMockStakingKeeper(ctrl)
// Return a dummy module address for the mint module.
accountKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(sdk.AccAddress{}).AnyTimes()
// Override the default mint function with our dummy inflation calculator.
s.mintKeeper = keeper.NewKeeper(
encCfg.Codec,
storeService,
s.stakingKeeper,
accountKeeper,
s.bankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
// Set default parameters.
err := s.mintKeeper.Params.Set(s.ctx, types.DefaultParams())
s.Require().NoError(err)
// Set a known dummy minter in the store for deterministic behavior.
s.Require().NoError(s.mintKeeper.Minter.Set(s.ctx, types.DefaultInitialMinter()))
}
// TestDefaultMintFn_Success tests the successful execution of the default mint function.
func (s *MintFnTestSuite) TestDefaultMintFn_Success() {
// Set the staking keeper expectations.
stakingSupply := math.NewInt(1_000_000_000)
bondedRatio := math.LegacyNewDecWithPrec(50, 2) // 0.50
s.stakingKeeper.EXPECT().StakingTokenSupply(s.ctx).Return(stakingSupply, nil).Times(1)
s.stakingKeeper.EXPECT().BondedRatio(s.ctx).Return(bondedRatio, nil).Times(1)
expectedCoins := sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(20)))
minter, err := s.mintKeeper.Minter.Get(s.ctx)
s.Require().NoError(err)
expectedInflation := types.DefaultInflationCalculationFn(context.TODO(), minter, types.DefaultParams(), bondedRatio)
// Set bank keeper expectations for minting and fee collection.
s.bankKeeper.EXPECT().MintCoins(s.ctx, types.ModuleName, expectedCoins).Return(nil).Times(1)
s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(s.ctx, types.ModuleName, authtypes.FeeCollectorName, expectedCoins).Return(nil).Times(1)
// Call the mint function.
err = s.mintKeeper.MintFn(s.ctx)
s.Require().NoError(err)
// Retrieve the updated minter from storage.
updatedMinter, err := s.mintKeeper.Minter.Get(s.ctx)
s.Require().NoError(err)
// check that minter values are updated as expected
s.Require().Equal(expectedInflation, updatedMinter.Inflation)
s.Require().Equal(expectedInflation.MulInt(stakingSupply), updatedMinter.AnnualProvisions)
// Optionally, verify that a mint event has been emitted.
events := s.ctx.EventManager().Events()
s.Require().True(slices.ContainsFunc(events, func(event sdk.Event) bool {
return event.Type == types.EventTypeMint
}), "expected a mint event to be emitted")
}
// customMintFn defines a custom minting function that overrides minter behavior.
func customMintFn(ctx sdk.Context, k *keeper.Keeper) error {
// Retrieve the current minter and parameters.
minter, err := k.Minter.Get(ctx)
if err != nil {
return err
}
_, err = k.Params.Get(ctx)
if err != nil {
return err
}
// Custom logic: override minter values.
minter.Inflation = math.LegacyMustNewDecFromStr("0.1")
minter.AnnualProvisions = math.LegacyMustNewDecFromStr("200")
if err := k.Minter.Set(ctx, minter); err != nil {
return err
}
// Instead of the default block provision, mint a custom coin.
mintedCoin := sdk.NewCoin("custom", math.NewInt(50))
mintedCoins := sdk.NewCoins(mintedCoin)
// Execute bank keeper methods.
if err := k.MintCoins(ctx, mintedCoins); err != nil {
return err
}
if err := k.AddCollectedFees(ctx, mintedCoins); err != nil {
return err
}
// Emit a custom event.
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx.EventManager().EmitEvent(sdk.NewEvent(
"custom_mint",
sdk.NewAttribute("custom_attribute", "true"),
))
return nil
}
// TestCustomMintFn tests the custom mint function.
func (s *MintFnTestSuite) TestCustomMintFn() {
// Reinitialize the keeper with the custom mint function.
encCfg := moduletestutil.MakeTestEncodingConfig(mint.AppModuleBasic{})
key := storetypes.NewKVStoreKey(types.StoreKey)
storeService := runtime.NewKVStoreService(key)
s.ctx = testutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test")).Ctx
ctrl := gomock.NewController(s.T())
accountKeeper := minttestutil.NewMockAccountKeeper(ctrl)
// Use fresh mocks for account keeper if needed.
accountKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(sdk.AccAddress{}).AnyTimes()
// Reuse the existing stakingKeeper and bankKeeper from the suite.
s.mintKeeper = keeper.NewKeeper(
encCfg.Codec,
storeService,
s.stakingKeeper,
accountKeeper,
s.bankKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
keeper.WithMintFn(customMintFn),
)
// Set default parameters and initial minter.
err := s.mintKeeper.Params.Set(s.ctx, types.DefaultParams())
s.Require().NoError(err)
s.Require().NoError(s.mintKeeper.Minter.Set(s.ctx, types.DefaultInitialMinter()))
// Expect bank keeper calls to be made for the custom minted coin.
expectedCoins := sdk.NewCoins(sdk.NewCoin("custom", math.NewInt(50)))
s.bankKeeper.EXPECT().MintCoins(s.ctx, types.ModuleName, expectedCoins).Return(nil).Times(1)
s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(s.ctx, types.ModuleName, authtypes.FeeCollectorName, expectedCoins).Return(nil).Times(1)
// Call the custom mint function.
err = s.mintKeeper.MintFn(s.ctx)
s.Require().NoError(err)
// Retrieve and verify the updated minter values.
storedMinter, err := s.mintKeeper.Minter.Get(s.ctx)
s.Require().NoError(err)
s.Require().Equal(math.LegacyMustNewDecFromStr("0.1"), storedMinter.Inflation)
s.Require().Equal(math.LegacyMustNewDecFromStr("200"), storedMinter.AnnualProvisions)
// Check that the custom mint event was emitted.
events := s.ctx.EventManager().Events()
s.Require().True(slices.ContainsFunc(events, func(event sdk.Event) bool {
return event.Type == "custom_mint"
}), "expected custom_mint event to be emitted")
}

View File

@ -92,10 +92,6 @@ type AppModule struct {
// legacySubspace is used solely for migration of x/params managed parameters
legacySubspace exported.Subspace
// 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
@ -104,19 +100,19 @@ func NewAppModule(
cdc codec.Codec,
keeper keeper.Keeper,
ak types.AccountKeeper,
// This input is unused as of Cosmos SDK v0.53 and will be removed in a future release of the Cosmos SDK.
ic types.InflationCalculationFn,
ss exported.Subspace,
) AppModule {
if ic == nil {
ic = types.DefaultInflationCalculationFn
if ic != nil {
panic("inflation calculation function argument must be nil as it is no longer used. This argument will be removed in a future release of the Cosmos SDK. To set a custom inflation calculation function, use the WithMintFn option when constructing the x/mint keeper as follows: mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(minttypes.DefaultInflationCalculationFn))")
}
return AppModule{
AppModuleBasic: AppModuleBasic{cdc: cdc},
keeper: keeper,
authKeeper: ak,
inflationCalculator: ic,
legacySubspace: ss,
AppModuleBasic: AppModuleBasic{cdc: cdc},
keeper: keeper,
authKeeper: ak,
legacySubspace: ss,
}
}
@ -160,7 +156,7 @@ func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion }
// BeginBlock returns the begin blocker for the mint module.
func (am AppModule) BeginBlock(ctx context.Context) error {
return BeginBlocker(ctx, am.keeper, am.inflationCalculator)
return BeginBlocker(ctx, am.keeper)
}
// AppModuleSimulation functions
@ -172,7 +168,7 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) {
// ProposalMsgs returns msgs used for governance proposals for simulations.
// migrate to ProposalMsgsX. This method is ignored when ProposalMsgsX exists and will be removed in the future.
func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg {
func (AppModule) ProposalMsgs(_ module.SimulationState) []simtypes.WeightedProposalMsg {
return simulation.ProposalMsgs()
}
@ -204,11 +200,13 @@ func init() {
type ModuleInputs struct {
depinject.In
ModuleKey depinject.OwnModuleKey
Config *modulev1.Module
StoreService store.KVStoreService
Cdc codec.Codec
ModuleKey depinject.OwnModuleKey
Config *modulev1.Module
StoreService store.KVStoreService
Cdc codec.Codec
// Deprecated: This input is unused as of Cosmos SDK v0.53 and will be removed in a future release of the Cosmos SDK.
InflationCalculationFn types.InflationCalculationFn `optional:"true"`
MintFn keeper.MintFn `optional:"true"`
// LegacySubspace is used solely for migration of x/params managed parameters
LegacySubspace exported.Subspace `optional:"true"`
@ -237,6 +235,15 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority)
}
if in.InflationCalculationFn != nil {
panic("inflation calculation function argument must be nil as it is no longer used. This argument will be removed in a future release of the Cosmos SDK. To set a custom inflation calculation function, while using depinject ")
}
var opts []keeper.InitOption
if in.MintFn != nil {
opts = append(opts, keeper.WithMintFn(in.MintFn))
}
k := keeper.NewKeeper(
in.Cdc,
in.StoreService,
@ -245,10 +252,11 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
in.BankKeeper,
feeCollectorName,
authority.String(),
opts...,
)
// when no inflation calculation function is provided it will use the default types.DefaultInflationCalculationFn
m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.InflationCalculationFn, in.LegacySubspace)
m := NewAppModule(in.Cdc, k, in.AccountKeeper, nil, in.LegacySubspace)
return ModuleOutputs{MintKeeper: k, Module: m}
}