银行(Bank)
摘要
本文档指定了 Cosmos SDK 的银行模块。 银行模块负责处理账户之间的多资产币种转移,并跟踪必须与特定类型的账户(尤其是为权益账户委托/取消委托)进行不同处理的特殊伪转移。它公开了具有不同能力的多个接口,以便与其他必须更改用户余额的模块进行安全交互。 此外,银行模块跟踪并提供对应用程序中所有资产的总供应量的查询支持。 该模块在 Cosmos Hub 中使用。
目录
供应
供应功能:
被动地跟踪链中所有币种的总供应量,
为模块提供持有/与币种交互的模式,和
引入不变式检查来验证链的总供应量。
总供应量
网络的总供应量等于所有账户中币种的总和。每当铸造(例如:作为通胀机制的一部分)或销毁(例如:由于惩罚或如果治理提案被否决)一个币种时,总供应量会被更新。
Module Accounts
供应功能引入了一种新的 auth.Account
类型,模块可以使用它来分配代币,并在特殊情况下铸造或销毁代币。在基本层面,这些模块账户能够向 auth.Account
和其他模块账户发送/接收代币。这种设计取代了之前的替代设计,之前模块为持有代币,会从发送者账户销毁传入的代币,然后在内部跟踪这些代币。后来,为了发送代币,模块需要在目标账户内有效地铸造代币。新设计消除了模块之间执行此会计的重复逻辑。
ModuleAccount
接口定义如下:
type ModuleAccount interface {
auth.Account // same methods as the Account interface
GetName() string // name of the module; used to obtain the address
GetPermissions() []string // permissions of module account
HasPermission(string) bool
}
WARNING! 任何允许直接或间接发送资金的模块或消息处理程序,必须明确保证这些资金不能发送到模块账户(除非允许)。
供应 Keeper
还引入了与 ModuleAccounts
相关的 auth Keeper
和 bank Keeper
的新包装函数,以便能够:
通过提供名称获取和设置
ModuleAccounts
。通过仅传递名称,从一个模块账户向其他模块账户或标准账户(
BaseAccount
或VestingAccount
)发送代币。为模块账户铸造或销毁代币(受其权限限制)。
许可
每个 ModuleAccount
都有不同的权限集,这些权限为其提供执行特定操作的不同能力。权限需要在创建供应 Keeper
时注册,以便每当一个 ModuleAccount
调用允许的函数时,Keeper
可以查找该特定账户的权限并决定是否执行该操作。
可用的权限包括:
Minter:允许模块铸造特定数量的代币。
Burner:允许模块销毁特定数量的代币。
Staking:允许模块委托和取消委托特定数量的代币。
状态
x/bank
模块保持以下主要对象的状态:
账户余额
面额元数据
所有余额的总供应量
哪些面额可以被发送的信息
此外,x/bank
模块还保持以下索引,以管理上述状态:
供应索引:
0x0 | byte(denom) -> byte(amount)
面额元数据索引:
0x1 | byte(denom) -> ProtocolBuffer(Metadata)
余额索引:
0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)
反向面额到地址索引:
0x03 | byte(denom) | 0x00 | []byte(address) -> 0
参数
银行模块将其参数存储在状态中,前缀为 0x05
,可以通过治理或具有权限的地址进行更新。
Params:
0x05 | ProtocolBuffer(Params)
https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/bank.proto#L12-L23
Keepers
银行模块提供了以下导出的 keeper
接口,可以传递给其他读取或更新账户余额的模块。模块应使用提供所需功能的最小权限接口。
最佳实践要求仔细审查银行模块代码,以确保权限限制符合预期。
拒绝的地址
x/bank
模块接受一个地址映射,这些地址被视为被列入黑名单,不能通过诸如 MsgSend
和 MsgMultiSend
等手段直接和明确地接收资金,也不能通过类似 SendCoinsFromModuleToAccount
的直接 API 调用接收资金。
通常,这些地址是模块账户。如果这些地址在不符合状态机预期规则的情况下接收资金,可能会导致不变式被破坏,从而导致网络停止。
通过为 x/bank
模块提供被列入黑名单的地址集,如果用户或客户端尝试通过 IBC 等方式直接或间接向被列入黑名单的账户发送资金,则该操作会发生错误。
通用类型
Input
多方转账的输入
// Input models transaction input.
message Input {
string address = 1;
repeated cosmos.base.v1beta1.Coin coins = 2;
}
Output
多方转账的输出
// Output models transaction outputs.
message Output {
string address = 1;
repeated cosmos.base.v1beta1.Coin coins = 2;
}
BaseKeeper
基础 keeper
提供完全权限访问:能够任意修改任何账户的余额以及铸造或销毁代币。
通过使用 baseKeeper
并结合 WithMintCoinsRestriction
,可以实现每个模块的铸造权限限制,以对铸造操作施加特定的限制(例如,仅铸造特定面额的代币)。
// Keeper defines a module interface that facilitates the transfer of coins
// between accounts.
type Keeper interface {
SendKeeper
WithMintCoinsRestriction(MintingRestrictionFn) BaseKeeper
InitGenesis(context.Context, *types.GenesisState)
ExportGenesis(context.Context) *types.GenesisState
GetSupply(ctx context.Context, denom string) sdk.Coin
HasSupply(ctx context.Context, denom string) bool
GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error)
IterateTotalSupply(ctx context.Context, cb func(sdk.Coin) bool)
GetDenomMetaData(ctx context.Context, denom string) (types.Metadata, bool)
HasDenomMetaData(ctx context.Context, denom string) bool
SetDenomMetaData(ctx context.Context, denomMetaData types.Metadata)
IterateAllDenomMetaData(ctx context.Context, cb func(types.Metadata) bool)
SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error
BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error
DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
// GetAuthority gets the address capable of executing governance proposal messages. Usually the gov module account.
GetAuthority() string
types.QueryServer
}
SendKeeper
send keeper
提供访问账户余额的权限,并能够在账户之间转移代币。send keeper
不会改变总供应量(即不会铸造或销毁代币)。
// SendKeeper defines a module interface that facilitates the transfer of coins
// between accounts without the possibility of creating coins.
type SendKeeper interface {
ViewKeeper
AppendSendRestriction(restriction SendRestrictionFn)
PrependSendRestriction(restriction SendRestrictionFn)
ClearSendRestriction()
InputOutputCoins(ctx sdk.Context, input types.Input, outputs []types.Output) error
SendCoins(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error
GetParams(ctx context.Context) types.Params
SetParams(ctx context.Context, params types.Params) error
IsSendEnabledDenom(ctx context.Context, denom string) bool
SetSendEnabled(ctx context.Context, denom string, value bool)
SetAllSendEnabled(ctx context.Context, sendEnableds []*types.SendEnabled)
DeleteSendEnabled(ctx context.Context, denom string)
IterateSendEnabledEntries(ctx context.Context, cb func(denom string, sendEnabled bool) (stop bool))
GetAllSendEnabledEntries(ctx context.Context) []types.SendEnabled
IsSendEnabledCoin(ctx context.Context, coin sdk.Coin) bool
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
BlockedAddr(addr sdk.AccAddress) bool
}
Send Restrictions
SendKeeper
在每次资金转移之前应用 SendRestrictionFn
限制函数。
// A SendRestrictionFn can restrict sends and/or provide a new receiver address.
type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (newToAddr sdk.AccAddress, err error)
在创建 SendKeeper
(或 BaseKeeper
)之后,可以使用 AppendSendRestriction
或 PrependSendRestriction
函数将发送限制添加到其中。这两个函数会将提供的限制与任何先前提供的限制组合起来。AppendSendRestriction
会将提供的限制添加到所有先前提供的发送限制之后执行,而 PrependSendRestriction
会将限制添加到所有先前提供的发送限制之前执行。组合时,如果遇到错误,将会短路,即如果第一个限制返回错误,则不会执行第二个限制。
在执行 SendCoins
时,发送限制会在从发件人地址移除代币后,但在将其添加到收件人地址之前应用。在执行 InputOutputCoins
时,发送限制会在移除输入代币后,且每次应用于每个输出,直到资金被添加。
发送限制函数应利用上下文中的自定义值来允许绕过特定的限制。
发送限制不会应用于 ModuleToAccount
或 ModuleToModule
转账。这是因为模块需要将资金转移到用户账户和其他模块账户。这个设计决策是为了在状态机中提供更大的灵活性。状态机应能够在模块账户和用户账户之间转移资金,而不受限制。
其次,这个限制会限制状态机本身的使用。如果强加此限制,用户将无法接收奖励,也无法在模块账户之间转移资金。例如,如果用户将资金从用户账户转移到社区池中,然后通过治理提案将这些代币转回用户账户,这将取决于应用链开发者如何处理。我们不能在这里做出过强的假设。
第三,这个问题可能会导致链停滞,如果禁用的代币在 begin/endblock
中被移动,这将对用户造成更大的损害,而不是好处。
例如,在您的模块的 keeper
包中,您可以定义发送限制函数:
var _ banktypes.SendRestrictionFn = Keeper{}.SendRestrictionFn
func (k Keeper) SendRestrictionFn(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) {
// Bypass if the context says to.
if mymodule.HasBypass(ctx) {
return toAddr, nil
}
// Your custom send restriction logic goes here.
return nil, errors.New("not implemented")
}
bank keeper
应该提供给您 keeper
的构造函数,以便将发送限制添加到其中:
func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bankKeeper mymodule.BankKeeper) Keeper {
rv := Keeper{/*...*/}
bankKeeper.AppendSendRestriction(rv.SendRestrictionFn)
return rv
}
然后,在 mymodule
包中,定义上下文辅助函数:
const bypassKey = "bypass-mymodule-restriction"
// WithBypass returns a new context that will cause the mymodule bank send restriction to be skipped.
func WithBypass(ctx context.Context) context.Context {
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, true)
}
// WithoutBypass returns a new context that will cause the mymodule bank send restriction to not be skipped.
func WithoutBypass(ctx context.Context) context.Context {
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, false)
}
// HasBypass checks the context to see if the mymodule bank send restriction should be skipped.
func HasBypass(ctx context.Context) bool {
bypassValue := ctx.Value(bypassKey)
if bypassValue == nil {
return false
}
bypass, isBool := bypassValue.(bool)
return isBool && bypass
}
在任何需要使用 SendCoins
或 InputOutputCoins
的地方,但不希望应用发送限制时:
func (k Keeper) DoThing(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error {
return k.bankKeeper.SendCoins(mymodule.WithBypass(ctx), fromAddr, toAddr, amt)
}
ViewKeeper
ViewKeeper
提供对账户余额的只读访问。ViewKeeper
不具备余额更改功能。所有余额查询的时间复杂度为 O(1)
。
// ViewKeeper defines a module interface that facilitates read only access to
// account balances.
type ViewKeeper interface {
ValidateBalance(ctx context.Context, addr sdk.AccAddress) error
HasBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) bool
GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins
GetAccountsBalances(ctx context.Context) []types.Balance
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
IterateAccountBalances(ctx context.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool))
IterateAllBalances(ctx context.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool))
}
消息
MsgSend
从一个地址向另一个地址发送代币。
https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L38-L53
该消息将在以下情况下失败:
代币未启用发送功能
目标地址受限
MsgMultiSend
从一个发送方地址向多个不同的地址发送代币。如果任一接收地址不存在,则会创建一个新账户。
https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L58-L69
该消息将在以下情况下失败:
任何代币未启用发送功能
任何目标地址受限
任何代币被锁定
输入与输出不匹配
MsgUpdateParams
bank
模块的参数可以通过 MsgUpdateParams
进行更新,该操作可以通过治理提案完成。
签名者始终是 gov
模块账户地址。
https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L74-L88
该消息处理可能会失败的情况:
签名者不是
gov
模块账户地址。
MsgSetSendEnabled
用于 x/gov
模块,以创建或编辑 SendEnabled
条目。
https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L96-L117
该消息将在以下情况下失败:
authority
不是bech32
地址。authority
不是x/gov
模块的地址。存在多个
SendEnabled
条目使用相同的Denom
。一个或多个
SendEnabled
条目包含无效的Denom
。
事件
bank
模块会触发以下事件:
消息事件
MsgSend
transfer
recipient
{recipientAddress}
transfer
amount
{amount}
message
module
bank
message
action
send
message
sender
{senderAddress}
MsgMultiSend
transfer
recipient
{recipientAddress}
transfer
amount
{amount}
message
module
bank
message
action
multisend
message
sender
{senderAddress}
Keeper 事件
除了消息事件之外,当调用以下方法(或任何最终调用这些方法的方法)时,BankKeeper
还会生成事件:
MintCoins
{
"type": "coinbase",
"attributes": [
{
"key": "minter",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being minted}}",
"index": true
}
]
}
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
BurnCoins
{
"type": "burn",
"attributes": [
{
"key": "burner",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
addCoins
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the address beneficiary of the coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
subUnlockedCoins/DelegateCoins
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the address which is spending coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being spent}}",
"index": true
}
]
}
参数
bank
模块包含以下参数:
SendEnabled
SendEnabled
参数现已弃用,不再使用。它已被状态存储记录替代。
DefaultSendEnabled
默认的 send enabled
值控制所有代币面额的发送转账功能,除非特别包含在 SendEnabled
参数的数组中。
客户端
CLI
用户可以使用 CLI 查询和与 bank
模块进行交互。
Query
查询命令允许用户查询 bank
状态。
simd query bank --help
balances
balances
命令允许用户通过地址查询账户余额。
simd query bank balances [address] [flags]
示例:
simd query bank balances cosmos1..
示例输出:
balances:
- amount: "1000000000"
denom: stake
pagination:
next_key: null
total: "0"
denom-metadata
denom-metadata
命令允许用户查询代币面额的元数据。用户可以使用 --denom
标志查询单个面额的元数据,或者不使用该标志查询所有面额的元数据。
simd query bank denom-metadata [flags]
示例:
simd query bank denom-metadata --denom stake
示例输出:
metadata:
base: stake
denom_units:
- aliases:
- STAKE
denom: stake
description: native staking token of simulation app
display: stake
name: SimApp Token
symbol: STK
total
total
命令允许用户查询代币的总供应量。用户可以使用 --denom
标志查询单个代币的总供应量,或者不使用该标志查询所有代币的总供应量。
simd query bank total [flags]
示例:
simd query bank total --denom stake
示例输出:
amount: "10000000000"
denom: stake
send-enabled
send-enabled
命令允许用户查询所有或部分 SendEnabled
条目。
simd query bank send-enabled [denom1 ...] [flags]
示例:
simd query bank send-enabled
示例输出:
send_enabled:
- denom: foocoin
enabled: true
- denom: barcoin
pagination:
next-key: null
total: 2
Transactions
tx
命令允许用户与 bank
模块进行交互。
simd tx bank --help
send
send
命令允许用户从一个账户向另一个账户发送资金。
simd tx bank send [from_key_or_address] [to_address] [amount] [flags]
示例:
simd tx bank send cosmos1.. cosmos1.. 100stake
gRPC
用户可以使用 gRPC 端点查询 bank
模块。
Balance
Balance
端点允许用户根据给定的代币面额通过地址查询账户余额。
cosmos.bank.v1beta1.Query/Balance
示例:
grpcurl -plaintext \
-d '{"address":"cosmos1..","denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/Balance
示例输出:
{
"balance": {
"denom": "stake",
"amount": "1000000000"
}
}
AllBalances
AllBalances
端点允许用户查询账户地址的所有代币面额的余额。
cosmos.bank.v1beta1.Query/AllBalances
示例:
grpcurl -plaintext \
-d '{"address":"cosmos1.."}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/AllBalances
示例输出:
{
"balances": [
{
"denom": "stake",
"amount": "1000000000"
}
],
"pagination": {
"total": "1"
}
}
DenomMetadata
DenomMetadata
端点允许用户查询单个代币面额的元数据。
cosmos.bank.v1beta1.Query/DenomMetadata
示例:
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomMetadata
示例输出:
{
"metadata": {
"description": "native staking token of simulation app",
"denomUnits": [
{
"denom": "stake",
"aliases": [
"STAKE"
]
}
],
"base": "stake",
"display": "stake",
"name": "SimApp Token",
"symbol": "STK"
}
}
DenomsMetadata
DenomsMetadata
端点允许用户查询所有代币面额的元数据。
cosmos.bank.v1beta1.Query/DenomsMetadata
示例:
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomsMetadata
示例输出:
{
"metadatas": [
{
"description": "native staking token of simulation app",
"denomUnits": [
{
"denom": "stake",
"aliases": [
"STAKE"
]
}
],
"base": "stake",
"display": "stake",
"name": "SimApp Token",
"symbol": "STK"
}
],
"pagination": {
"total": "1"
}
}
DenomOwners
DenomOwners
端点允许用户查询单个代币面额的元数据。
cosmos.bank.v1beta1.Query/DenomOwners
示例:
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomOwners
示例输出:
{
"denomOwners": [
{
"address": "cosmos1..",
"balance": {
"denom": "stake",
"amount": "5000000000"
}
},
{
"address": "cosmos1..",
"balance": {
"denom": "stake",
"amount": "5000000000"
}
},
],
"pagination": {
"total": "2"
}
}
TotalSupply
TotalSupply
端点允许用户查询所有代币的总供应量。
cosmos.bank.v1beta1.Query/TotalSupply
示例:
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/TotalSupply
示例输出:
{
"supply": [
{
"denom": "stake",
"amount": "10000000000"
}
],
"pagination": {
"total": "1"
}
}
SupplyOf
SupplyOf
端点允许用户查询单个代币的总供应量。
cosmos.bank.v1beta1.Query/SupplyOf
示例:
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/SupplyOf
示例输出:
{
"amount": {
"denom": "stake",
"amount": "10000000000"
}
}
Params
Params
端点允许用户查询 bank
模块的参数。
cosmos.bank.v1beta1.Query/Params
示例:
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/Params
示例输出:
{
"params": {
"defaultSendEnabled": true
}
}
SendEnabled
SendEnabled
端点允许用户查询 bank
模块的 SendEnabled
条目。
任何未返回的代币面额将使用 Params.DefaultSendEnabled
值。
cosmos.bank.v1beta1.Query/SendEnabled
示例:
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/SendEnabled
示例输出:
{
"send_enabled": [
{
"denom": "foocoin",
"enabled": true
},
{
"denom": "barcoin"
}
],
"pagination": {
"next-key": null,
"total": 2
}
}
Last updated