- 区块链
【免费下载链接】cosmos-sdk
Framework for building performant, customizable blockchains with native interoperability
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" 一节):
- 允许由市场供需决定的弹性通胀率,其锚点是某个目标质押比例(goal %-bonded,即目标 bonded 比例);
- 在市场流动性与质押供给之间取得平衡。
为了实现这一点,模块采用移动变化率(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:
| 条目 | 存储前缀 | 内容 |
|---|---|---|
| Minter | 0x00 | 当前通胀信息(Inflation与AnnualProvisions) |
| Params | 0x01 | 模块参数(可通过治理或具有 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,其执行顺序如下:
- 从状态读取
minter与params; - 通过 staking keeper 获取
totalStakingSupply(总质押供给)与bondedRatio(质押比例); - 调用通胀计算函数
ic(ctx, minter, params, bondedRatio)更新minter.Inflation; - 调用
minter.NextAnnualProvisions(params, totalStakingSupply)更新年化供给; - 将更新后的
minter写回状态; - 调用
minter.BlockProvision(params)计算本区块铸币数量; - MaxSupply 截断检查(可选功能):
- 当
MaxSupply非零且totalSupply + mintedCoin > MaxSupply时,把铸币量裁剪为MaxSupply - totalSupply; - 若总供给已超过上限,则本次不铸币(
diff置为 0); - 该参数独立于铸币流程本身,代币总量调整(包括销毁)由外部模块负责;
- 当
- 通过 bank keeper 的
MintCoins从ModuleMinterAccount(即mint模块账户)铸造代币(sdk.NewCoins会自动跳过零金额,见 x/mint/keeper/keeper.go 的MintCoins); - 通过
AddCollectedFees(内部调用SendCoinsFromModuleToModule)把代币转入auth模块的FeeCollector模块账户; - 发射
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 | 类型 | 默认值示例 |
|---|---|---|
| MintDenom | string | "uatom" |
| InflationRateChange | string (dec) | "0.130000000000000000" |
| InflationMax | string (dec) | "0.200000000000000000" |
| InflationMin | string (dec) | "0.070000000000000000" |
| GoalBonded | string (dec) | "0.670000000000000000" |
| BlocksPerYear | string (uint64) | "6311520" |
| MaxSupply | string (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):
| Type | Attribute Key | Attribute Value |
|---|---|---|
| mint | bonded_ratio | {bondedRatio} |
| mint | inflation | {inflation} |
| mint | annual_provisions | {annualProvisions} |
| mint | amount | {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 --helpannual-provisions——查询当前年化铸币量:
simd query mint annual-provisions [flags]示例输出:
22268504368893.612100895088410693inflation——查询当前通胀率:
simd query mint inflation [flags]示例输出:
0.199200302563256955params——查询当前铸币参数:
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/AnnualProvisionsgrpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/AnnualProvisions示例输出:
{ "annualProvisions": "1432452520532626265712995618" }Inflation:
/cosmos.mint.v1beta1.Query/Inflationgrpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Inflation示例输出:
{ "inflation": "130197115720711261" }Params:
/cosmos.mint.v1beta1.Query/Paramsgrpcurl -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_provisionscurl "localhost:1317/cosmos/mint/v1beta1/annual_provisions"示例输出:
{ "annualProvisions": "1432452520532626265712995618" }inflation:
/cosmos/mint/v1beta1/inflationcurl "localhost:1317/cosmos/mint/v1beta1/inflation"示例输出:
{ "inflation": "130197115720711261" }params:
/cosmos/mint/v1beta1/paramscurl "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
相关推荐
Diem 框架核心模块深度解析:`0x1::Diem` 货币体系、铸造(Mint)与销毁(Burn)机制全指南
Diem 框架核心模块深度解析: 0x1::Diem 货币体系、铸造(Mint)与销毁(Burn)机制全指南 导读 :本文以 Diem 框架(Diem Fram
区块链金融科技yuzu Switch 模拟器使用指南:30 秒判定硬件 + 5 步跑通第一款游戏
yuzu Switch 模拟器使用指南:30 秒判定硬件 + 5 步跑通第一款游戏 yuzu 是一款免费开源的 Switch 模拟器,用软件完整还原 CPU、内
虚拟化图形学逆向工程Cosmos SDK x/epochs 模块完全指南:链上定时器、Epoch 钩子与跨模块周期调度
Cosmos SDK x/epochs 模块完全指南:链上定时器、Epoch 钩子与跨模块周期调度 导读 x/epochs 是 Cosmos SDK 中负责提供
区块链
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考