☰
cosmos-sdk x/mint 模块完全指南:通胀铸币机制、自定义 MintFn 与查询接口
2026/10/12 3:12:22 网站建设 项目流程
  • 区块链

【免费下载链接】cosmos-sdk

Framework for building performant, customizable blockchains with native interoperability

项目地址:https://gitcode.com/gh_mirrors/co/cosmos-sdk
点击查看免费下载

x/mint是 Cosmos SDK 中负责按可配置策略周期性铸造新代币的模块:它在每个区块开始时根据链上质押(bonded)比例动态调整年化通胀率,并把铸出的代币转入FeeCollector模块账户。本文基于仓库内 x/mint/README.md 与x/mint目录下的 keeper、types、module、proto 等源码,完整讲解其铸币机制、状态存储、Begin-Block 流程、七个核心参数、自定义 Minter 扩展点(Cosmos SDK v0.53.0 引入的MintFn),以及 CLI、gRPC、REST 三套查询方式,帮助你掌握在自建链上配置与定制通胀铸币的完整方案。

Concepts:通胀铸币机制的设计目标

默认铸币机制的设计目标有两点(见 x/mint/README.md 的 "The Minting Mechanism" 一节):

  1. 允许由市场供需决定的弹性通胀率,其锚点是某个目标质押比例(goal %-bonded,即目标 bonded 比例);
  2. 在市场流动性与质押供给之间取得平衡。

为了实现这一点,模块采用移动变化率(moving change rate)机制:当实际质押比例偏离目标值时,通胀率会向相反方向调整,从而激励或抑制质押行为。把目标质押比例设定在 100% 以下(Cosmos Hub 中为 67%),能鼓励网络保留一部分未质押代币以提供流动性。

该机制可以归纳为三条规则:

  • 当实际质押代币比例低于目标比例时,通胀率持续上升,直到达到最大值(InflationMax);
  • 当实际质押比例等于目标比例(Cosmos-Hub 为 67%)时,通胀率保持不变;
  • 当实际质押比例高于目标比例时,通胀率持续下降,直到达到最小值(InflationMin)。

这一逻辑的源码实现位于 x/mint/types/minter.go 的NextInflationRate方法中(下文详述)。

State:模块状态存储

x/mint的状态由两个 collections 条目构成,存储键定义在 x/mint/types/keys.go:

条目存储前缀内容
Minter0x00当前通胀信息(Inflation与AnnualProvisions)
Params0x01模块参数(可通过治理或具有 authority 的地址更新)

Minter

Minter是存放当前通胀信息的空间,对应 protobuf 消息定义见 proto/cosmos/mint/v1beta1/mint.proto:

// Minter represents the minting state. message Minter { // current annual inflation rate string inflation = 1; // current annual expected provisions string annual_provisions = 2; }

两者均为cosmossdk.io/math.LegacyDec类型(精度为 18 位小数的十进制数)。初始 Minter 由 genesis 写入,仓库默认初始通胀率为 13%(见 x/mint/types/minter.go 的DefaultInitialMinter)。

Params

Params以0x01为前缀存入状态,可通过治理提案或 authority 地址(默认是x/gov模块账户)更新。注意:最新版本新增了MaxSupply参数,用于控制模块铸造代币的最大供应量,值为0表示无上限。完整字段定义同样位于 proto/cosmos/mint/v1beta1/mint.proto:

message Params { // type of coin to mint string mint_denom = 1; // maximum annual change in inflation rate string inflation_rate_change = 2; // maximum inflation rate string inflation_max = 3; // minimum inflation rate string inflation_min = 4; // goal of percent bonded atoms string goal_bonded = 5; // expected blocks per year uint64 blocks_per_year = 6; // maximum supply for the token. // // A value of "0" indicates an unlimited (infinite) maximum supply. string max_supply = 7; }

所有 decimal 字段(inflation_rate_change、inflation_max、inflation_min、goal_bonded)都带有amino.dont_omitempty = true选项,因此在 legacy Amino JSON 兼容编码中,即使是"0"也会被编码输出。

Begin-Block:每区块的铸币流程

铸币参数在每个区块开始时重新计算并支付通胀奖励(Begin-Block 阶段执行)。入口函数在 x/mint/abci.go:

// BeginBlocker mints new tokens for the previous block. func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyBeginBlocker) sdkCtx := sdk.UnwrapSDKContext(ctx) return k.MintFn(sdkCtx) }

BeginBlocker只是透传调用 Keeper 的MintFn。Keeper 在创建时默认挂载DefaultMintFn(见 x/mint/keeper/keeper.go 的NewKeeper),也可通过WithMintFn选项替换(见下文"自定义 Minter"一节)。

默认铸币函数 DefaultMintFn 的完整调用链

DefaultMintFn的实现位于 x/mint/keeper/mint.go,其执行顺序如下:

  1. 从状态读取minter与params;
  2. 通过 staking keeper 获取totalStakingSupply(总质押供给)与bondedRatio(质押比例);
  3. 调用通胀计算函数ic(ctx, minter, params, bondedRatio)更新minter.Inflation;
  4. 调用minter.NextAnnualProvisions(params, totalStakingSupply)更新年化供给;
  5. 将更新后的minter写回状态;
  6. 调用minter.BlockProvision(params)计算本区块铸币数量;
  7. MaxSupply 截断检查(可选功能):
    • 当MaxSupply非零且totalSupply + mintedCoin > MaxSupply时,把铸币量裁剪为MaxSupply - totalSupply;
    • 若总供给已超过上限,则本次不铸币(diff置为 0);
    • 该参数独立于铸币流程本身,代币总量调整(包括销毁)由外部模块负责;
  8. 通过 bank keeper 的MintCoins从ModuleMinterAccount(即mint模块账户)铸造代币(sdk.NewCoins会自动跳过零金额,见 x/mint/keeper/keeper.go 的MintCoins);
  9. 通过AddCollectedFees(内部调用SendCoinsFromModuleToModule)把代币转入auth模块的FeeCollector模块账户;
  10. 发射mint类型事件并更新遥测指标。

这段 MaxSupply 逻辑的源码如下(x/mint/keeper/mint.go):

maxSupply := params.MaxSupply totalSupply := k.bankKeeper.GetSupply(ctx, params.MintDenom).Amount // fetch total supply from the bank module // if maxSupply is not infinite, and minted coins exceeds maxSupply, adjust minted coins to be the diff if !maxSupply.IsZero() && totalSupply.Add(mintedCoin.Amount).GT(maxSupply) { // calculate the difference between maxSupply and totalSupply diff := maxSupply.Sub(totalSupply) if diff.Sign() == -1 { // mint nothing if total supply already exceeds max supply diff = sdkmath.ZeroInt() } // mint the difference mintedCoin.Amount = diff }

对应的事件发射(x/mint/keeper/mint.go):

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

通胀率计算:InflationCalculationFn

通胀率由"通胀计算函数"(inflation calculation function)计算,它通过NewAppModule传入。注意版本变化:在 Cosmos SDK v0.53 中该参数已弃用——NewAppModule的ic参数必须传nil,否则会直接 panic(见 x/mint/module.go);自定义通胀计算逻辑现在应通过WithMintFn(mintkeeper.DefaultMintFn(customIC))的方式传入自定义的InflationCalculationFn。函数签名定义在 x/mint/types/genesis.go:

type InflationCalculationFn func(ctx context.Context, minter Minter, params Params, bondedRatio math.LegacyDec) math.LegacyDec

默认实现DefaultInflationCalculationFn只是透传调用minter.NextInflationRate(params, bondedRatio)。

NextInflationRate:通胀率如何随质押比例调整

目标年化通胀率每个区块重新计算一次,调整幅度取决于与目标比例(67%)的距离。年化通胀率变化上限为 13%/年,通胀率整体被钳制在 7% 到 20% 之间。源码(x/mint/types/minter.go):

func (m Minter) NextInflationRate(params Params, bondedRatio math.LegacyDec) math.LegacyDec { // (1 - bondedRatio/GoalBonded) * InflationRateChange inflationRateChangePerYear := math.LegacyOneDec(). Sub(bondedRatio.Quo(params.GoalBonded)). Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(math.LegacyNewDec(int64(params.BlocksPerYear))) // adjust the new annual inflation for this next block 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 }

关键推导关系:

  • inflationRateChangePerYear = (1 - bondedRatio / GoalBonded) * InflationRateChange;
  • 每区块变化量 = 年变化量 /BlocksPerYear;
  • 当bondedRatio < GoalBonded时该值为正(通胀上升),等于时为零(通胀不变),大于时为负(通胀下降);
  • 最终结果被InflationMax与InflationMin双向钳制。

NextAnnualProvisions:年化铸币总量

根据当前总供给与通胀率计算年化铸币总量,每个区块计算一次(x/mint/types/minter.go):

func (m Minter) NextAnnualProvisions(_ Params, totalSupply math.Int) math.LegacyDec { return m.Inflation.MulInt(totalSupply) }

即AnnualProvisions = Inflation × totalSupply。

BlockProvision:每区块铸币量

根据年化铸币总量与BlocksPerYear计算每个区块的铸币量,随后由mint模块账户铸造并转给auth的FeeCollector模块账户(x/mint/types/minter.go):

func (m Minter) BlockProvision(params Params) sdk.Coin { provisionAmt := m.AnnualProvisions.QuoInt(math.NewInt(int64(params.BlocksPerYear))) return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt()) }

即BlockProvision = AnnualProvisions / BlocksPerYear,向下截断为整数代币(mint_denom对应面额)。

自定义 Minter:MintFn 与 WithMintFn

从Cosmos SDK v0.53.0开始,开发者可以通过设置自定义MintFn实现专门的代币铸币逻辑。MintFn的类型定义与执行入口位于 x/mint/keeper/mint.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

创建 Keeper 时通过Option传入自定义MintFn(WithMintFn定义在 x/mint/keeper/keeper.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 )

这段代码与仓库中 simapp/app.go 的真实实例化方式一致(simapp 中保留了WithMintFn的注释示例)。注意:NewKeeper内部总是先以DefaultMintFn(types.DefaultInflationCalculationFn)初始化,WithMintFn选项随后覆盖它。

自定义 Minter 的 DI 示例

下面是一个在 DI(依赖注入)配置中创建带额外依赖的自定义铸币函数的简单示例——让 minter 直接把foo币的供给翻倍。

首先,定义一个接收所需依赖并返回MintFn的函数:

// MyCustomMintFunction is a custom mint function that doubles the supply of `foo` coin. func MyCustomMintFunction(bank bankkeeper.BaseKeeper) mintkeeper.MintFn { return func(ctx sdk.Context, k *mintkeeper.Keeper) error { supply := bank.GetSupply(ctx, "foo") err := k.MintCoins(ctx, sdk.NewCoins(supply.Add(supply))) if err != nil { return err } return nil } }

然后把该函数连同所需依赖一起传入depinject.Supply:

// NewSimApp returns a reference to an initialized SimApp. func NewSimApp( logger log.Logger, db dbm.DB, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { var ( app = &SimApp{} appBuilder *runtime.AppBuilder appConfig = depinject.Configs( AppConfig, depinject.Supply( appOpts, logger, // our custom mint function with the necessary dependency passed in. MyCustomMintFunction(app.BankKeeper), ), ) ) // ... }

DI 装配的底层支持位于 x/mint/module.go 的ProvideModule:ModuleInputs中声明了MintFn keeper.MintFn(optional:"true"),当它非空时会被包装为keeper.WithMintFn(in.MintFn)传入NewKeeper。

测试验证:仓库 x/mint/keeper/mint_test.go 提供了两个对照测试:

  • TestDefaultMintFn_Success:设置 staking supply 为1_000_000_000、bondedRatio 为0.50,断言通胀率与年化供给按默认公式更新,且mint事件被发射;
  • TestCustomMintFn:通过keeper.WithMintFn(customMintFn)注入自定义函数(铸 50 个custom币并发射custom_mint事件),断言 minter 值与自定义事件均生效。

Parameters:七个核心参数详解

x/mint模块包含以下参数(MaxSupply为0表示无上限):

Key类型默认值示例
MintDenomstring"uatom"
InflationRateChangestring (dec)"0.130000000000000000"
InflationMaxstring (dec)"0.200000000000000000"
InflationMinstring (dec)"0.070000000000000000"
GoalBondedstring (dec)"0.670000000000000000"
BlocksPerYearstring (uint64)"6311520"
MaxSupplystring (math.Int)"0"

默认值在 x/mint/types/params.go 的DefaultParams中定义:InflationRateChange = 13%、InflationMax = 20%、InflationMin = 7%、GoalBonded = 67%、BlocksPerYear = 60 * 60 * 8766 / 5(即假设 5 秒一个区块的一年区块数,约 6311520)、MaxSupply = 0(无限供给)。

参数校验规则(Params.Validate,位于同一文件):

  • mint_denom不能为空且必须通过sdk.ValidateDenom;
  • inflation_rate_change不能为 nil、不能为负、不能大于 1;
  • inflation_max/inflation_min不能为 nil、不能为负、不能大于 1;
  • goal_bonded必须为正、不能大于 1;
  • blocks_per_year必须为正且不超过MaxInt64;
  • max_supply不能为负;
  • 额外约束:inflation_max必须大于等于inflation_min。

参数更新:通过MsgUpdateParams消息完成,处理逻辑在 x/mint/keeper/msg_server.go 的UpdateParams——先校验msg.Authority是否匹配 keeper 的authority(默认x/gov模块账户),再校验新参数,最后写入状态。对应的 CLI 命令是simd tx mint update-params-proposal [params](定义见 x/mint/autocli.go,注意要求提供完整参数,可通过simd query mint params --output json查看字段格式)。

Events:模块事件

铸币模块在 BeginBlocker 中发射mint类型事件(事件常量定义于 x/mint/types/events.go,发射逻辑在 x/mint/keeper/mint.go):

TypeAttribute KeyAttribute Value
mintbonded_ratio{bondedRatio}
mintinflation{inflation}
mintannual_provisions{annualProvisions}
mintamount{amount}

Genesis 初始化

模块的 genesis 状态由Minter与Params两部分组成(proto/cosmos/mint/v1beta1/genesis.proto)。InitGenesis会把二者写入 collections 存储并确保mint模块账户存在,ExportGenesis则反向导出(x/mint/keeper/genesis.go)。默认 genesis 使用DefaultInitialMinter()(通胀率 13%)与DefaultParams()(x/mint/types/genesis.go)。当前模块共识版本为ConsensusVersion = 3(x/mint/module.go),并注册了从 v2 到 v3 的迁移(对应MaxSupply参数的引入,见 x/mint/keeper/migrator.go)。

Client:CLI、gRPC 与 REST 查询

CLI

用户可以通过 CLI 查询与交互mint模块(AutoCLI 命令定义见 x/mint/autocli.go):

simd query mint --help

annual-provisions——查询当前年化铸币量:

simd query mint annual-provisions [flags]

示例输出:

22268504368893.612100895088410693

inflation——查询当前通胀率:

simd query mint inflation [flags]

示例输出:

0.199200302563256955

params——查询当前铸币参数:

simd query mint params [flags]

示例输出:

blocks_per_year: "4360000" goal_bonded: "0.670000000000000000" inflation_max: "0.200000000000000000" inflation_min: "0.070000000000000000" inflation_rate_change: "0.130000000000000000" mint_denom: stake max_supply: "0"

gRPC

gRPC 查询服务定义于 proto/cosmos/mint/v1beta1/query.proto,三个 RPC 端点分别对应Params、Inflation、AnnualProvisions。实现位于 x/mint/keeper/grpc_query.go,它们直接从 collections 存储读取Params条目或Minter条目的字段。

AnnualProvisions:

/cosmos.mint.v1beta1.Query/AnnualProvisions
grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/AnnualProvisions

示例输出:

{ "annualProvisions": "1432452520532626265712995618" }

Inflation:

/cosmos.mint.v1beta1.Query/Inflation
grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Inflation

示例输出:

{ "inflation": "130197115720711261" }

Params:

/cosmos.mint.v1beta1.Query/Params
grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Params

示例输出:

{ "params": { "mintDenom": "stake", "inflationRateChange": "130000000000000000", "inflationMax": "200000000000000000", "inflationMin": "70000000000000000", "goalBonded": "670000000000000000", "blocksPerYear": "6311520", "maxSupply": "0", } }

REST

REST 端点与 gRPC 服务一一对应(HTTP GET 路由注解同样定义于 proto/cosmos/mint/v1beta1/query.proto,由 gRPC Gateway 提供):

annual-provisions:

/cosmos/mint/v1beta1/annual_provisions
curl "localhost:1317/cosmos/mint/v1beta1/annual_provisions"

示例输出:

{ "annualProvisions": "1432452520532626265712995618" }

inflation:

/cosmos/mint/v1beta1/inflation
curl "localhost:1317/cosmos/mint/v1beta1/inflation"

示例输出:

{ "inflation": "130197115720711261" }

params:

/cosmos/mint/v1beta1/params
curl "localhost:1317/cosmos/mint/v1beta1/params"

示例输出:

{ "params": { "mintDenom": "stake", "inflationRateChange": "130000000000000000", "inflationMax": "200000000000000000", "inflationMin": "70000000000000000", "goalBonded": "670000000000000000", "blocksPerYear": "6311520", "maxSupply": "0", } }

小结

x/mint模块通过"目标质押比例 + 移动变化率"机制实现弹性通胀:每个区块开始时由BeginBlocker触发MintFn,依次计算通胀率、年化铸币量、每区块铸币量,经MaxSupply上限检查后从mint模块账户铸币并转入FeeCollector。从 v0.53.0 起,MintFn与WithMintFn提供了完整的状态级自定义入口,InflationCalculationFn则可在保留默认流程的前提下替换通胀计算公式;配合MaxSupply上限参数、MsgUpdateParams治理更新以及 CLI/gRPC/REST 三套查询接口,开发者可以按需塑造自建链的代币发行策略。需要动手实践时,可参考仓库的 simapp/app.go 中的 Keeper 装配方式,以及 x/mint/keeper/mint_test.go 中的默认/自定义铸币函数测试用例。

  • 区块链

【免费下载链接】cosmos-sdk

Framework for building performant, customizable blockchains with native interoperability

项目地址:https://gitcode.com/gh_mirrors/co/cosmos-sdk
点击查看免费下载

相关推荐

上一篇:MJRefresh 与代码覆盖率工具:使用 Slather 生成覆盖率报告
下一篇:15分钟打造专业图标选择器:react-icons组件开发指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询