摘要

本文档规定了 Cosmos SDK 的治理模块,该模块最早于 2016 年 6 月在 Cosmos Whitepaper 中描述。 该模块使基于 Cosmos SDK 的区块链能够支持链上治理系统。在这个系统中,链的原生质押代币持有者可以按照 1 代币 1 票的方式对提案进行投票。以下是该模块当前支持的功能列表:
  • 提交提案: 用户可以通过缴纳押金来提交提案。一旦达到最低押金,提案就会进入投票期。最低押金可以在押金期内通过收集不同用户(包括提案人)的押金来达到。
  • 投票: 参与者可以对已达到 MinDeposit 并进入投票期的提案进行投票。
  • 继承与惩罚: 如果委托人自己不投票,则会继承其验证人的投票。
  • 领取押金: 对提案缴纳押金的用户可以在提案被接受或被否决后取回押金。如果提案被否决性否决,或始终未进入投票期(即在押金期内未达到最低押金),则押金会被销毁。
该模块已在 Cosmos Hub(也称 gaia)中使用。未来可能添加的功能见未来改进。

目录

以下规范使用 ATOM 作为原生质押代币。该模块可以通过将 ATOM 替换为链的原生质押代币,适配到任何权益证明区块链。

概念

治理流程分为以下几个步骤:
  • 提交提案: 向区块链提交带有押金的提案。
  • 投票: 一旦押金达到某个数值(MinDeposit),提案即被确认并开启投票。已绑定的 Atom 持有者随后可以发送 TxGovVote 交易,对该提案进行投票。
  • 执行 在一段时间后,会对投票结果进行统计,并根据结果执行提案中的消息。

提交提案

提交提案的权利

每个账户都可以通过发送 MsgSubmitProposal 交易来提交提案。提案一经提交,即通过其唯一的 proposalID 进行标识。

提案消息

提案包含一个 sdk.Msg 数组,如果提案通过,这些消息将被自动执行。消息由治理 ModuleAccount 自身执行。像 x/upgrade 这样的模块,如果希望某些消息只能由治理执行,应在各自的消息服务器中添加白名单,在达到法定人数后授予治理模块执行该消息的权限。治理模块使用 MsgServiceRouter 检查这些消息是否构造正确,并且是否具有相应的执行路径,但不会执行完整的有效性检查。

押金

为防止垃圾提案,提案必须使用 MinDeposit 参数定义的币种并附带押金进行提交。 提案提交时必须附带押金,押金额必须严格大于零,但可以小于 MinDeposit。提交者无需独自支付全部押金。新创建的提案会存储在非活跃提案队列中,并一直保留在那里,直到其押金超过 MinDeposit。其他代币持有者可以通过发送 Deposit 交易来增加提案的押金。如果某个提案在押金结束时间之前(即不再接受押金的时间)未达到 MinDeposit,该提案将被销毁:提案会从状态中移除,押金会被销毁(参见 x/gov EndBlocker)。如果提案押金在押金结束时间之前达到 MinDeposit 阈值(即使是在提案提交过程中达到),该提案将被移入活跃提案队列,并开始进入投票期。 押金将被托管,并由治理 ModuleAccount 持有,直到提案最终完成(通过或被否决)。

押金退还与销毁

当提案最终完成时,押金币种会根据提案的最终计票结果被退还或销毁:
  • 如果提案被批准或被否决,但未被否决性否决,则每笔押金都会自动退还给相应的押金支付者(从治理 ModuleAccount 转出)。
  • 当提案以超过 1/3 的比例被否决性否决时,押金将从治理 ModuleAccount 中销毁,同时提案信息及其押金信息会从状态中移除。
  • 所有被退还或被销毁的押金都会从状态中移除。在销毁或退还押金时会发出事件。

投票

参与者

参与者是指有权对提案进行投票的用户。在 Cosmos Hub 上,参与者是已绑定的 Atom 持有者。未绑定的 Atom 持有者和其他用户没有参与治理的权利。但是,他们可以提交提案并为提案缴纳押金。 请注意,当参与者同时持有已绑定和未绑定的 Atom 时,其投票权仅根据已绑定的 Atom 持仓计算。

投票期

一旦提案达到 MinDeposit,它将立即进入 Voting period。我们将 Voting period 定义为从投票开启到投票结束之间的时间区间。Voting period 的初始值为 2 周。

选项集

提案的选项集是指参与者在投票时可选择的选项集合。 初始选项集包括以下选项:
  • Yes
  • No
  • NoWithVeto
  • Abstain
NoWithVeto 会按 No 计票,同时也会增加一张 Veto 票。Abstain 选项允许投票者表明其无意支持或反对该提案,但接受投票结果。 注意:从 UI 角度看,对于紧急提案,我们也许应该增加一个“Not Urgent”选项,该选项会投出一张 NoWithVeto 票。

加权投票

ADR-037 引入了加权投票功能,允许质押者将自己的投票拆分到多个投票选项中。例如,它可以使用 70% 的投票权投 Yes,并使用 30% 的投票权投 No。 很多时候,拥有某个地址的实体并不一定是单一个人。例如,一家公司可能拥有希望以不同方式投票的多个利益相关方,因此允许他们拆分投票权是合理的。目前,他们还无法进行“passthrough voting”,也无法将其代币的投票权直接赋予用户。不过,通过这个系统,交易所可以先收集用户的投票偏好,然后按照投票调查结果的比例在链上投票。 为了在链上表示加权投票,我们使用以下 Protobuf 消息。
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L34-L47
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L181-L201
要使加权投票有效,options 字段中不能包含重复的投票选项,并且所有选项的权重之和必须等于 1。

自定义投票计算

Cosmos SDK v0.53.0 引入了一个选项,允许开发者定义自定义的投票结果和投票权计算函数。从 v0.54 开始,x/gov 已与 x/staking 解耦:keeper.NewKeeper 构造函数现在不再接收 StakingKeeper,而是要求将 CalculateVoteResultsAndVotingPowerFn 作为必需参数传入。要使用默认的基于 staking 的计票逻辑,请使用 keeper.NewDefaultCalculateVoteResultsAndVotingPower(stakingKeeper) 包装你的 staking keeper。
package keeper

import (
    
	"context"
    "fmt"
    "cosmossdk.io/collections"
    "cosmossdk.io/math"

	sdk "github.com/cosmos/cosmos-sdk/types"
	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// CalculateVoteResultsAndVotingPowerFn is a function signature for calculating vote results and voting power
// It can be overridden to customize the voting power calculation for proposals
// It gets the proposal tallied and the validators governance infos (validator power, voting power, etc.)
// It must return the total voting power and the results of the vote
type CalculateVoteResultsAndVotingPowerFn func(
	ctx context.Context,
	k Keeper,
	proposal v1.Proposal,
	validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error)

func defaultCalculateVoteResultsAndVotingPower(
	ctx context.Context,
	k Keeper,
	proposal v1.Proposal,
	validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) {
    totalVotingPower := math.LegacyZeroDec()

results = make(map[v1.VoteOption]math.LegacyDec)

results[v1.OptionYes] = math.LegacyZeroDec()

results[v1.OptionAbstain] = math.LegacyZeroDec()

results[v1.OptionNo] = math.LegacyZeroDec()

results[v1.OptionNoWithVeto] = math.LegacyZeroDec()
    rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
    votesToRemove := []collections.Pair[uint64, sdk.AccAddress]{
}

err = k.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) {
		// if validator, just record it in the map
		voter, err := k.authKeeper.AddressCodec().StringToBytes(vote.Voter)
    if err != nil {
    return false, err
}

valAddrStr, err := k.sk.ValidatorAddressCodec().BytesToString(voter)
    if err != nil {
    return false, err
}
    if val, ok := validators[valAddrStr]; ok {
    val.Vote = vote.Options
			validators[valAddrStr] = val
}

		// iterate over all delegations from voter, deduct from any delegated-to validators
		err = k.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) {
    valAddrStr := delegation.GetValidatorAddr()
    if val, ok := validators[valAddrStr]; ok {
				// There is no need to handle the special case that validator address equal to voter address.
				// Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator.
				val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares())

validators[valAddrStr] = val

				// delegation shares * bonded / total shares
    votingPower := delegation.GetShares().MulInt(val.ValidatorPower).Quo(val.DelegatorShares)
    for _, option := range vote.Options {
    weight, _ := math.LegacyNewDecFromStr(option.Weight)
    subPower := votingPower.Mul(weight)

results[option.Option] = results[option.Option].Add(subPower)
}

totalVotingPower = totalVotingPower.Add(votingPower)
}

return false
})
    if err != nil {
    return false, err
}

votesToRemove = append(votesToRemove, key)

return false, nil
})
    if err != nil {
    return math.LegacyZeroDec(), nil, fmt.Errorf("error while iterating delegations: %w", err)
}

	// remove all votes from store
    for _, key := range votesToRemove {
    if err := k.Votes.Remove(ctx, key); err != nil {
    return math.LegacyDec{
}, nil, fmt.Errorf("error while removing vote (%d/%s): %w", key.K1(), key.K2(), err)
}
	
}

	// iterate over the validators again to tally their voting power
    for _, val := range validators {
    if len(val.Vote) == 0 {
    continue
}
    sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions)
    votingPower := sharesAfterDeductions.MulInt(val.ValidatorPower).Quo(val.DelegatorShares)
    for _, option := range val.Vote {
    weight, _ := math.LegacyNewDecFromStr(option.Weight)
    subPower := votingPower.Mul(weight)

results[option.Option] = results[option.Option].Add(subPower)
}

totalVotingPower = totalVotingPower.Add(votingPower)
}

return totalVotingPower, results, nil
}

// getCurrentValidators fetches all the bonded validators, insert them into currValidators
func (k Keeper)

getCurrentValidators(ctx context.Context) (map[string]v1.ValidatorGovInfo, error) {
    currValidators := make(map[string]v1.ValidatorGovInfo)
    if err := k.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
    valBz, err := k.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
    if err != nil {
    return false
}

currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo(
			valBz,
			validator.GetValidatorPower(),
			validator.GetDelegatorShares(),
			math.LegacyZeroDec(),
			v1.WeightedVoteOptions{
},
		)

return false
}); err != nil {
    return nil, err
}

return currValidators, nil
}

// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the
// voters
func (k Keeper)

Tally(ctx context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) {
    currValidators, err := k.getCurrentValidators(ctx)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while getting current validators: %w", err)
}
    tallyFn := k.calculateVoteResultsAndVotingPowerFn
	totalVotingPower, results, err := tallyFn(ctx, k, proposal, currValidators)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while calculating tally results: %w", err)
}

tallyResults = v1.NewTallyResultFromMap(results)

	// TODO: Upgrade the spec to cover all of these cases & remove pseudocode.
	// If there is no staked coins, the proposal fails
	totalBonded, err := k.sk.TotalValidatorPower(ctx)
    if err != nil {
    return false, false, tallyResults, err
}
    if totalBonded.IsZero() {
    return false, false, tallyResults, nil
}

params, err := k.Params.Get(ctx)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while getting params: %w", err)
}

	// If there is not enough quorum of votes, the proposal fails
    percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded))

quorum, _ := math.LegacyNewDecFromStr(params.Quorum)
    if percentVoting.LT(quorum) {
    return false, params.BurnVoteQuorum, tallyResults, nil
}

	// If no one votes (everyone abstains), proposal fails
    if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) {
    return false, false, tallyResults, nil
}

	// If more than 1/3 of voters veto, proposal fails
	vetoThreshold, _ := math.LegacyNewDecFromStr(params.VetoThreshold)
    if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) {
    return false, params.BurnVoteVeto, tallyResults, nil
}

	// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
	// For expedited 2/3
	var thresholdStr string
    if proposal.Expedited {
    thresholdStr = params.GetExpeditedThreshold()
}

else {
    thresholdStr = params.GetThreshold()
}

threshold, _ := math.LegacyNewDecFromStr(thresholdStr)
    if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) {
    return true, false, tallyResults, nil
}

	// If more than 1/2 of non-abstaining voters vote No, proposal fails
	return false, false, tallyResults, nil
}
这为开发者在其应用链上处理治理提供了更具表达力的方式。 开发者现在可以构建如下系统:
  • 二次投票
  • 时间加权投票
  • 基于声誉的投票
示例
func myCustomVotingFunction(
  ctx context.Context,
  k Keeper,
  proposal v1.Proposal,
  validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) {
  // ... tally logic
}
    govKeeper := govkeeper.NewKeeper(
  appCodec,
  runtime.NewKVStoreService(keys[govtypes.StoreKey]),
  app.AccountKeeper,
  app.BankKeeper,
  app.DistrKeeper, // optional: can be nil if the module address is not used as a cancellation fee destination
  app.MsgServiceRouter(),
  govConfig,
  authtypes.NewModuleAddress(govtypes.ModuleName).String(),
  myCustomVotingFunction, // required: CalculateVoteResultsAndVotingPowerFn
)

法定人数

法定人数定义为:为使提案结果有效,必须在提案上投出的最小投票权百分比。

加急提案

提案可以被设为加急,这会使提案默认采用更短的投票时长和更高的计票阈值。如果加急提案未能在较短投票时长内达到阈值,则该加急提案会被转换为常规提案,并在常规投票条件下重新开始投票。

阈值

阈值定义为:提案被接受所需的 Yes 票最小占比(不包括 Abstain 票)。 初始情况下,阈值设定为 Yes 票的 50%,不包括 Abstain 票。如果所有投票中超过 1/3 为 NoWithVeto 票,则存在否决的可能。注意,这两个值都来源于链上的 TallyParams 参数,并且可由治理进行修改。 这意味着,提案被接受当且仅当:
  • 存在已绑定的代币。
  • 已达到法定人数。
  • Abstain 票的占比小于 1/1。
  • NoWithVeto 票的占比小于 1/3,其中包括 Abstain 票。
  • 在投票期结束时,排除 Abstain 票后,Yes 票的占比 高于 1/2。
对于加急提案,默认情况下,其阈值高于普通提案,即 66.7%。

继承

如果委托人未投票,它将继承其验证者的投票。
  • 如果委托人在其验证者之前投票,则不会继承该 验证者的投票。
  • 如果委托人在其验证者之后投票,则会用自己的投票覆盖其验证者的 投票。如果提案较为紧急,则可能 在委托人有机会做出反应并 覆盖其验证者投票之前,投票就已结束。这不是问题,因为提案只有在投票期结束计票时,获得超过总投票权 2/3 的支持才会通过。因为只需 1/3 + 1 的验证者投票权就可能串通审查交易,所以对于超过该阈值的范围,系统本就假设不存在串通。

验证者未投票的惩罚

目前,验证者未投票不会受到惩罚。

治理地址

未来,我们可能会添加带权限的密钥,使其只能为某些模块发起的交易签名。对于 MVP,Governance address 将是账户创建时生成的主验证者地址。该地址对应的 PrivKey 与负责签署共识消息的 CometBFT PrivKey 不同。因此,验证者无需使用敏感的 CometBFT PrivKey 来签署治理交易。

可销毁参数

有三个参数用于定义提案的押金应被销毁还是退还给押金支付者。
  • BurnVoteVeto:如果提案被否决,则销毁该提案的押金。
  • BurnVoteQuorum:如果投票未达到法定人数,则销毁该提案的押金。
  • BurnProposalDepositPrevote:如果提案未进入投票阶段,则销毁该提案的押金。
注意:这些参数可通过治理进行修改。

状态

宪章

Constitution 位于创世状态中。它是一个字符串字段,用于描述特定区块链的目标及其预期规范。constitution 字段的一些使用示例如下:
  • 定义链的目标,为其未来发展奠定基础
  • 设定对委托人的预期
  • 设定对验证者的预期
  • 定义链与“现实世界”实体(如基金会或公司)之间的关系
由于这更偏向社会性特性而非技术性特性,下面我们来看一些适合写入创世宪章的内容:
  • 治理存在哪些限制(如果有)?
    • 如果社区不再希望某个巨鲸继续存在,是否可以对其钱包执行 slash?(参见:Juno Proposal 4 和 16)
    • 治理是否可以对使用未获批准 MEV 的验证者进行“社会性 slash”?(参见:commonwealth.im/osmosis)
    • 如果发生经济紧急情况,验证者应当怎么做?
      • 在 2022 年 5 月 Terra 崩盘期间,由于治理代币被严重增发而几乎失去价值,验证者选择运行一个未经治理批准的新二进制版本。
  • 这条链的具体目标是什么?
    • 最典型的例子是 Cosmos Hub,不同的创始团队对网络目标有不同的理解。
这个创世项 constitution 并不是为现有链设计的,现有链更可能应通过自身的治理系统来批准一份宪章。相反,它是为新链准备的。它将使验证者在运行节点时更清楚地理解链的目标以及对他们的预期。同样,对于社区成员来说,宪章也能让他们对“链团队”和验证者分别应承担什么角色有一个基本预期。 这份宪章被设计为不可变,并且仅放置在创世状态中。不过,这一点未来也可能通过向 cosmos-sdk 提交 pull request 而改变,从而允许通过治理修改宪章。希望对原始宪章进行修订的社区,应使用治理机制以及“信号型提案”来实现这一点。 cosmos 链宪章的理想使用场景 作为链开发者,你决定为关键用户群体提供更清晰的说明:
  • 验证者
  • 代币持有者
  • 开发者(你自己)
你使用宪章在创世状态中不可变地存储一些 Markdown 内容,以便当出现棘手问题时,宪章能够为社区提供指导。

提案

Proposal 对象用于统计投票结果,并总体上跟踪提案状态。 它们包含一个任意 sdk.Msg 的数组,治理模块会在提案通过时尝试解析并执行这些消息。Proposal 通过唯一 id 进行标识,并包含一组时间戳:submit_time、deposit_end_time、voting_start_time、voting_end_time,用于跟踪提案的生命周期。
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L51-L99
通常,仅靠一组消息不足以说明提案的目的;提案还需要更充分的论证,并为感兴趣的参与者提供讨论和辩论该提案的途径。 在大多数情况下,推荐使用链下系统来支持链上治理流程。 为此,提案包含一个特殊的 metadata 字段,它是一个字符串, 可用于为提案补充上下文。metadata 字段允许网络进行自定义使用, 不过通常预期该字段包含一个 URL,或使用类似 IPFS 系统的某种 CID。为了支持 跨网络的互操作性场景,SDK 建议 metadata 使用如下 JSON 模板:
{
  "title": "...",
  "description": "...",
  "forum": "...", // a link to the discussion platform (i.e. Discord)
  "other": "..." // any extra data that doesn't correspond to the other fields
}
这会让客户端更容易支持多个网络。 metadata 的最大长度由应用开发者决定, 并作为配置传入 gov keeper。SDK 中默认的最大长度为 255 个字符。

编写使用治理的模块

链本身或各个独立模块的许多方面,都可能希望通过治理来执行,例如修改各种参数。这实现起来非常简单。 首先,编写你的消息类型以及 MsgServer 实现。然后在 keeper 中添加一个 authority 字段,并在构造函数中使用治理模块账户进行填充:govKeeper.GetGovernanceAccount().GetAddress()。接着在 msg_server.go 的相关方法中,对消息执行检查,确认签名者 与 authority 匹配。这样可以防止任何用户执行该消息。

参数与基础类型

Parameters 定义了投票运行所遵循的规则。在任意时刻,只能 有一个处于激活状态的参数集。如果治理希望变更某个 参数集,无论是修改某个值还是新增/删除参数字段,都必须创建一个新的 参数集,并将之前的参数集置为非激活状态。

DepositParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L152-L162

VotingParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L164-L168

TallyParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L170-L182
参数存储在全局 GlobalParams KVStore 中。 此外,我们还引入了一些基础类型:
type Vote byte

const (
    VoteYes         = 0x1
    VoteNo          = 0x2
    VoteNoWithVeto  = 0x3
    VoteAbstain     = 0x4
)

type ProposalType  string

const (
    ProposalTypePlainText       = "Text"
    ProposalTypeSoftwareUpgrade = "SoftwareUpgrade"
)

type ProposalStatus byte

const (
    StatusNil           ProposalStatus = 0x00
    StatusDepositPeriod ProposalStatus = 0x01  // Proposal is submitted. Participants can deposit on it but not vote
    StatusVotingPeriod  ProposalStatus = 0x02  // MinDeposit is reached, participants can vote
    StatusPassed        ProposalStatus = 0x03  // Proposal passed and successfully executed
    StatusRejected      ProposalStatus = 0x04  // Proposal has been rejected
    StatusFailed        ProposalStatus = 0x05  // Proposal passed but failed execution
)

押金

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L38-L49

ValidatorGovInfo

此类型在统计投票时用于临时映射。
type ValidatorGovInfo struct {
    Minus     sdk.Dec
    Vote      Vote
}

存储

存储是 multi-store 中的 KVStore。用于查找该存储的 key 是列表中的第一个参数。
我们将使用一个 KVStore Governance 来存储四种映射:
  • 从 proposalID|'proposal' 到 Proposal 的映射。
  • 从 proposalID|'addresses'|address 到 Vote 的映射。该映射允许 通过对 proposalID:addresses 执行范围查询,查询所有对该提案投票的地址及其投票内容。
  • 从 ParamsKey|'Params' 到 Params 的映射。该映射允许查询所有 x/gov 参数。
  • 从 VotingPeriodProposalKeyPrefix|proposalID 到单字节的映射。这使得 我们能够以非常低的 gas 成本判断提案是否处于投票期。
为了便于伪代码说明,下面是我们将用于从存储中读取或写入的两个函数:
  • load(StoreKey, Key):从 multistore 中 key 为 StoreKey 的存储里,读取存储在 key Key 下的条目
  • store(StoreKey, Key, value):向 multistore 中 key 为 StoreKey 的存储里,在 key Key 处写入值 Value

提案处理队列

存储:
  • ProposalProcessingQueue:一个队列 queue[proposalID],其中包含所有达到 MinDeposit 的提案的 ProposalIDs。在每个 EndBlock 期间, 所有已到达投票期结束时间的提案都会被处理。 为了处理一个已结束的提案,应用会统计投票、计算每个验证者的 票数,并检查验证者集合中的每个验证者是否都已投票。 如果提案被接受,则退还押金。最后,执行提案内容的 Handler。
下面是 ProposalProcessingQueue 的伪代码:
in EndBlock do
    for finishedProposalID in GetAllFinishedProposalIDs(block.Time)

proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key

      validators = Keeper.getAllValidators()
    tmpValMap := map(sdk.AccAddress)

ValidatorGovInfo

      // Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes
    for each validator in validators
        tmpValMap(validator.OperatorAddr).Minus = 0

      // Tally
      voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
    for each (voterAddress, vote)

in voterIterator
        delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter
    for each delegation in delegations
          // make sure delegation.Shares does NOT include shares being unbonded
          tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
          proposal.updateTally(vote, delegation.Shares)

        _, isVal = stakingKeeper.getValidator(voterAddress)
    if (isVal)

tmpValMap(voterAddress).Vote = vote

      tallyingParam = load(GlobalParams, 'TallyingParam')

      // Update tally if validator voted
    for each validator in validators
    if tmpValMap(validator).HasVoted
          proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))

      // Check if proposal is accepted or rejected
    totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
    if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain  < tallyingParam.Veto)
        //  proposal was accepted at the end of the voting period
        //  refund deposits (non-voters already punished)
    for each (amount, depositor)

in proposal.Deposits
          depositor.AtomBalance += amount

        stateWriter, err := proposal.Handler()
    if err != nil
            // proposal passed but failed during state execution
            proposal.CurrentStatus = ProposalStatusFailed
         else
            // proposal pass and state is persisted
            proposal.CurrentStatus = ProposalStatusAccepted
            stateWriter.save()

else
        // proposal was rejected
        proposal.CurrentStatus = ProposalStatusRejected

      store(Governance, <proposalID|'proposal'>, proposal)

旧版提案

旧版提案已废弃。请通过授予治理模块执行该消息的权限来使用新的提案流程。
旧版提案是治理提案的旧实现。 与可以包含任意消息的提案不同,旧版提案只允许提交一组预定义的提案。 这些提案由其类型定义,并由在 gov v1beta1 路由器中注册的处理器进行处理。 有关如何提交提案的更多信息,请参见客户端部分。

消息

提案提交

任何账户都可以通过 MsgSubmitProposal 交易提交提案。
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L42-L69
传入 MsgSubmitProposal 消息的 messages 字段中的所有 sdk.Msgs 都必须在应用的 MsgServiceRouter 中注册。每条此类消息都必须 只有一个签名者,即 gov 模块账户。最后,元数据长度 不得大于传递给 gov keeper 的 maxMetadataLen 配置。 initialDeposit 必须严格为正,并且符合 MinDeposit 参数接受的 denom。 状态修改:
  • 生成新的 proposalID
  • 创建新的 Proposal
  • 初始化 Proposal 的属性
  • 将发送者余额减少 InitialDeposit
  • 如果达到 MinDeposit:
    • 将 proposalID 推入 ProposalProcessingQueue
  • 将 InitialDeposit 从 Proposer 转移到治理 ModuleAccount

存款

提案提交后,如果 Proposal.TotalDeposit < ActiveParam.MinDeposit,Atom 持有者可以发送 MsgDeposit 交易来增加提案的存款。 存款仅在满足以下条件时才会被接受:
  • 提案存在
  • 提案不处于投票期
  • 存入的代币符合 MinDeposit 参数所接受的 denom
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L134-L147
状态修改:
  • 将发送者余额减少 deposit
  • 将发送者的 deposit 添加到 proposal.Deposits
  • 将 proposal.TotalDeposit 增加发送者的 deposit
  • 如果达到 MinDeposit:
    • 将 proposalID 推入 ProposalProcessingQueueEnd
  • 将 Deposit 从 proposer 转移到治理 ModuleAccount

投票

一旦达到 ActiveParam.MinDeposit,投票期就会开始。此后, 已质押的 Atom 持有者可以发送 MsgVote 交易来对该提案 进行投票。
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L92-L108
状态修改:
  • 记录发送者的 Vote
此消息的 Gas 成本必须考虑到未来在 EndBlocker 中对该投票进行计票的开销。

事件

治理模块会发出以下事件:

EndBlocker

类型属性键属性值
inactive_proposalproposal_id{proposalID}
inactive_proposalproposal_result{proposalResult}
active_proposalproposal_id{proposalID}
active_proposalproposal_result{proposalResult}

处理器

MsgSubmitProposal

类型属性键属性值
submit_proposalproposal_id{proposalID}
submit_proposal [0]voting_period_start{proposalID}
proposal_depositamount{depositAmount}
proposal_depositproposal_id{proposalID}
messagemodulegovernance
messageactionsubmit_proposal
messagesender{senderAddress}
  • [0] 仅在提交期间开始投票期时才会发出该事件。

MsgVote

类型属性键属性值
proposal_voteoption{voteOption}
proposal_voteproposal_id{proposalID}
messagemodulegovernance
messageactionvote
messagesender{senderAddress}

MsgVoteWeighted

类型属性键属性值
proposal_voteoption{weightedVoteOptions}
proposal_voteproposal_id{proposalID}
messagemodulegovernance
messageactionvote
messagesender{senderAddress}

MsgDeposit

类型属性键属性值
proposal_depositamount{depositAmount}
proposal_depositproposal_id{proposalID}
proposal_deposit [0]voting_period_start{proposalID}
messagemodulegovernance
messageactiondeposit
messagesender{senderAddress}
  • [0] 仅在提交期间开始投票期时才会发出该事件。

Hooks

治理模块暴露了一个 GovHooks 接口,允许其他模块对治理事件作出响应。
type GovHooks interface {
    AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error
    AfterProposalDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress) error
    AfterProposalVote(ctx context.Context, proposalID uint64, voterAddr sdk.AccAddress) error
    AfterProposalFailedMinDeposit(ctx context.Context, proposalID uint64) error
    AfterProposalVotingPeriodEnded(ctx context.Context, proposalID uint64) error
}

AfterProposalSubmission

在提案提交后调用。该 hook 会接收提案 ID 和提案者地址。 注意: proposerAddr 参数是在最近的版本中新增的。如果你正在实现 GovHooks,则必须更新 AfterProposalSubmission 方法签名,将 proposerAddr sdk.AccAddress 作为第三个参数加入。 之前:
func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64) error {
    // implementation
}
之后:
func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error {
    // implementation
}

AfterProposalDeposit

在为提案存款后调用。

AfterProposalVote

在对提案投票后调用。

AfterProposalFailedMinDeposit

当提案在存款期内未达到最低存款要求时调用。

AfterProposalVotingPeriodEnded

当提案的投票期结束时调用。

参数

治理模块包含以下参数:
键类型示例
min_depositarray (coins)[{"denom":"uatom","amount":"10000000"}]
max_deposit_periodstring (time ns)“172800000000000” (17280s)
voting_periodstring (time ns)“172800000000000” (17280s)
quorumstring (dec)“0.334000000000000000”
thresholdstring (dec)“0.500000000000000000”
vetostring (dec)“0.334000000000000000”
expedited_thresholdstring (time ns)“0.667000000000000000”
expedited_voting_periodstring (time ns)“86400000000000” (8600s)
expedited_min_depositarray (coins)[{"denom":"uatom","amount":"50000000"}]
burn_proposal_deposit_prevoteboolfalse
burn_vote_quorumboolfalse
burn_vote_vetobooltrue
min_initial_deposit_ratiostring”0.1”
注意:与其他模块不同,治理模块包含一些对象类型的参数。 如果只希望修改其中一部分参数,只需要包含这些参数, 而不需要提供完整的参数对象结构。

客户端

CLI

用户可以使用 CLI 查询并与 gov 模块交互。

查询

query 命令允许用户查询 gov 状态。
simd query gov --help
deposit
deposit 命令允许用户查询指定提案中某个存款人的存款。
simd query gov deposit [proposal-id] [depositer-addr] [flags]
示例:
simd query gov deposit 1 cosmos1..
示例输出:
amount:
- amount: "100"
  denom: stake
depositor: cosmos1..
proposal_id: "1"
deposits
deposits 命令允许用户查询指定提案的所有存款。
simd query gov deposits [proposal-id] [flags]
示例:
simd query gov deposits 1
示例输出:
deposits:
- amount:
  - amount: "100"
    denom: stake
  depositor: cosmos1..
  proposal_id: "1"
pagination:
  next_key: null
  total: "0"
param
param 命令允许用户查询 gov 模块中的指定参数。
simd query gov param [param-type] [flags]
示例:
simd query gov param voting
示例输出:
voting_period: "172800000000000"
params
params 命令允许用户查询 gov 模块的所有参数。
simd query gov params [flags]
示例:
simd query gov params
示例输出:
deposit_params:
  max_deposit_period: 172800s
  min_deposit:
  - amount: "10000000"
    denom: stake
params:
  expedited_min_deposit:
  - amount: "50000000"
    denom: stake
  expedited_threshold: "0.670000000000000000"
  expedited_voting_period: 86400s
  max_deposit_period: 172800s
  min_deposit:
  - amount: "10000000"
    denom: stake
  min_initial_deposit_ratio: "0.000000000000000000"
  proposal_cancel_burn_rate: "0.500000000000000000"
  quorum: "0.334000000000000000"
  threshold: "0.500000000000000000"
  veto_threshold: "0.334000000000000000"
  voting_period: 172800s
tally_params:
  quorum: "0.334000000000000000"
  threshold: "0.500000000000000000"
  veto_threshold: "0.334000000000000000"
voting_params:
  voting_period: 172800s
proposal
proposal 命令允许用户查询指定提案。
simd query gov proposal [proposal-id] [flags]
示例:
simd query gov proposal 1
示例输出:
deposit_end_time: "2022-03-30T11:50:20.819676256Z"
final_tally_result:
  abstain_count: "0"
  no_count: "0"
  no_with_veto_count: "0"
  yes_count: "0"
id: "1"
messages:
- '@type': /cosmos.bank.v1beta1.MsgSend
  amount:
  - amount: "10"
    denom: stake
  from_address: cosmos1..
  to_address: cosmos1..
metadata: AQ==
status: PROPOSAL_STATUS_DEPOSIT_PERIOD
submit_time: "2022-03-28T11:50:20.819676256Z"
total_deposit:
- amount: "10"
  denom: stake
voting_end_time: null
voting_start_time: null
proposals
proposals 命令允许用户在可选过滤条件下查询所有提案。
simd query gov proposals [flags]
示例:
simd query gov proposals
示例输出:
pagination:
  next_key: null
  total: "0"
proposals:
- deposit_end_time: "2022-03-30T11:50:20.819676256Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "1"
  messages:
  - '@type': /cosmos.bank.v1beta1.MsgSend
    amount:
    - amount: "10"
      denom: stake
    from_address: cosmos1..
    to_address: cosmos1..
  metadata: AQ==
  status: PROPOSAL_STATUS_DEPOSIT_PERIOD
  submit_time: "2022-03-28T11:50:20.819676256Z"
  total_deposit:
  - amount: "10"
    denom: stake
  voting_end_time: null
  voting_start_time: null
- deposit_end_time: "2022-03-30T14:02:41.165025015Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "2"
  messages:
  - '@type': /cosmos.bank.v1beta1.MsgSend
    amount:
    - amount: "10"
      denom: stake
    from_address: cosmos1..
    to_address: cosmos1..
  metadata: AQ==
  status: PROPOSAL_STATUS_DEPOSIT_PERIOD
  submit_time: "2022-03-28T14:02:41.165025015Z"
  total_deposit:
  - amount: "10"
    denom: stake
  voting_end_time: null
  voting_start_time: null
proposer
proposer 命令允许用户查询指定提案的提案人。
simd query gov proposer [proposal-id] [flags]
示例:
simd query gov proposer 1
示例输出:
proposal_id: "1"
proposer: cosmos1..
tally
tally 命令允许用户查询指定提案投票的计票结果。
simd query gov tally [proposal-id] [flags]
示例:
simd query gov tally 1
示例输出:
abstain: "0"
"no": "0"
no_with_veto: "0"
"yes": "1"
vote
vote 命令允许用户查询指定提案的一条投票记录。
simd query gov vote [proposal-id] [voter-addr] [flags]
示例:
simd query gov vote 1 cosmos1..
示例输出:
option: VOTE_OPTION_YES
options:
- option: VOTE_OPTION_YES
  weight: "1.000000000000000000"
proposal_id: "1"
voter: cosmos1..
votes
votes 命令允许用户查询指定提案的所有投票记录。
simd query gov votes [proposal-id] [flags]
示例:
simd query gov votes 1
示例输出:
pagination:
  next_key: null
  total: "0"
votes:
- option: VOTE_OPTION_YES
  options:
  - option: VOTE_OPTION_YES
    weight: "1.000000000000000000"
  proposal_id: "1"
  voter: cosmos1..

交易

tx 命令允许用户与 gov 模块交互。
simd tx gov --help
deposit
deposit 命令允许用户为指定提案存入代币。
simd tx gov deposit [proposal-id] [deposit] [flags]
示例:
simd tx gov deposit 1 10000000stake --from cosmos1..
draft-proposal
draft-proposal 命令允许用户起草任意类型的提案。 该命令会返回一个 draft_proposal.json,在补全后可由 submit-proposal 使用。 draft_metadata.json 用于上传到 IPFS。
simd tx gov draft-proposal
submit-proposal
submit-proposal 命令允许用户连同一些消息和元数据一起提交治理提案。 消息、元数据和押金在一个 JSON 文件中定义。
simd tx gov submit-proposal [path-to-proposal-json] [flags]
示例:
simd tx gov submit-proposal /path/to/proposal.json --from cosmos1..
其中 proposal.json 包含:
{
  "messages": [
    {
  "@type": "/cosmos.bank.v1beta1.MsgSend",
  "from_address": "cosmos1...", // The gov module module address
      "to_address": "cosmos1...",
  "amount":[{
  "denom": "stake",
  "amount": "10"}]
    }
  ],
  "metadata": "AQ==",
  "deposit": "10stake",
  "title": "Proposal Title",
  "summary": "Proposal Summary"
}
默认情况下,metadata、summary 和 title 都限制为 255 个字符,应用开发者可以覆盖此限制。
未指定 metadata 时,title 限制为 255 个字符,summary 限制为 title 长度的 40 倍。
submit-legacy-proposal
submit-legacy-proposal 命令允许用户连同初始押金一起提交治理旧版提案。
simd tx gov submit-legacy-proposal [command] [flags]
示例:
simd tx gov submit-legacy-proposal --title="Test Proposal" --description="testing" --type="Text" --deposit="100000000stake" --from cosmos1..
示例(param-change):
simd tx gov submit-legacy-proposal param-change proposal.json --from cosmos1..
{
  "title": "Test Proposal",
  "description": "testing, testing, 1, 2, 3",
  "changes": [
    {
      "subspace": "staking",
      "key": "MaxValidators",
      "value": 100
    }
  ],
  "deposit": "10000000stake"
}

cancel-proposal

提案被取消后,提案押金中的 deposits * proposal_cancel_ratio 将被销毁,或发送到 ProposalCancelDest 地址;如果 ProposalCancelDest 为空,则押金会被销毁。remaining deposits 将发送给存款人。
simd tx gov cancel-proposal [proposal-id] [flags]
示例:
simd tx gov cancel-proposal 1 --from cosmos1...
vote
vote 命令允许用户为指定治理提案提交投票。
simd tx gov vote [command] [flags]
示例:
simd tx gov vote 1 yes --from cosmos1..
weighted-vote
weighted-vote 命令允许用户为指定治理提案提交加权投票。
simd tx gov weighted-vote [proposal-id] [weighted-options] [flags]
示例:
simd tx gov weighted-vote 1 yes=0.5,no=0.5 --from cosmos1..

gRPC

用户可以使用 gRPC 端点查询 gov 模块。

Proposal

Proposal 端点允许用户查询指定提案。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Proposal
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Proposal
示例输出:
{
  "proposal": {
    "proposalId": "1",
    "content": {"@type":"/cosmos.gov.v1beta1.TextProposal","description":"testing, testing, 1, 2, 3","title":"Test Proposal"},
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "finalTallyResult": {
      "yes": "0",
      "abstain": "0",
      "no": "0",
      "noWithVeto": "0"
    },
    "submitTime": "2021-09-16T19:40:08.712440474Z",
    "depositEndTime": "2021-09-18T19:40:08.712440474Z",
    "totalDeposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "votingStartTime": "2021-09-16T19:40:08.712440474Z",
    "votingEndTime": "2021-09-18T19:40:08.712440474Z",
    "title": "Test Proposal",
    "summary": "testing, testing, 1, 2, 3"
  }
}
使用 v1:
cosmos.gov.v1.Query/Proposal
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Proposal
示例输出:
{
  "proposal": {
    "id": "1",
    "messages": [
      {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
    ],
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "finalTallyResult": {
      "yesCount": "0",
      "abstainCount": "0",
      "noCount": "0",
      "noWithVetoCount": "0"
    },
    "submitTime": "2022-03-28T11:50:20.819676256Z",
    "depositEndTime": "2022-03-30T11:50:20.819676256Z",
    "totalDeposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "votingStartTime": "2022-03-28T14:25:26.644857113Z",
    "votingEndTime": "2022-03-30T14:25:26.644857113Z",
    "metadata": "AQ==",
    "title": "Test Proposal",
    "summary": "testing, testing, 1, 2, 3"
  }
}

Proposals

Proposals 端点允许用户在可选过滤条件下查询所有提案。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Proposals
示例:
grpcurl -plaintext \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Proposals
示例输出:
{
  "proposals": [
    {
      "proposalId": "1",
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "finalTallyResult": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "noWithVeto": "0"
      },
      "submitTime": "2022-03-28T11:50:20.819676256Z",
      "depositEndTime": "2022-03-30T11:50:20.819676256Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "votingStartTime": "2022-03-28T14:25:26.644857113Z",
      "votingEndTime": "2022-03-30T14:25:26.644857113Z"
    },
    {
      "proposalId": "2",
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "finalTallyResult": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "noWithVeto": "0"
      },
      "submitTime": "2022-03-28T14:02:41.165025015Z",
      "depositEndTime": "2022-03-30T14:02:41.165025015Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "votingStartTime": "0001-01-01T00:00:00Z",
      "votingEndTime": "0001-01-01T00:00:00Z"
    }
  ],
  "pagination": {
    "total": "2"
  }
}

使用 v1:
cosmos.gov.v1.Query/Proposals
示例:
grpcurl -plaintext \
    localhost:9090 \
    cosmos.gov.v1.Query/Proposals
示例输出:
{
  "proposals": [
    {
      "id": "1",
      "messages": [
        {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
      ],
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "finalTallyResult": {
        "yesCount": "0",
        "abstainCount": "0",
        "noCount": "0",
        "noWithVetoCount": "0"
      },
      "submitTime": "2022-03-28T11:50:20.819676256Z",
      "depositEndTime": "2022-03-30T11:50:20.819676256Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "votingStartTime": "2022-03-28T14:25:26.644857113Z",
      "votingEndTime": "2022-03-30T14:25:26.644857113Z",
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    },
    {
      "id": "2",
      "messages": [
        {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
      ],
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "finalTallyResult": {
        "yesCount": "0",
        "abstainCount": "0",
        "noCount": "0",
        "noWithVetoCount": "0"
      },
      "submitTime": "2022-03-28T14:02:41.165025015Z",
      "depositEndTime": "2022-03-30T14:02:41.165025015Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    }
  ],
  "pagination": {
    "total": "2"
  }
}

投票

Vote 端点允许用户查询指定提案的一条投票记录。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Vote
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1","voter":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Vote
示例输出:
{
  "vote": {
    "proposalId": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1000000000000000000"
      }
    ]
  }
}
使用 v1:
cosmos.gov.v1.Query/Vote
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1","voter":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Vote
示例输出:
{
  "vote": {
    "proposalId": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ]
  }
}

投票列表

Votes 端点允许用户查询指定提案的全部投票记录。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Votes
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Votes
示例输出:
{
  "votes": [
    {
      "proposalId": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}
使用 v1:
cosmos.gov.v1.Query/Votes
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Votes
示例输出:
{
  "votes": [
    {
      "proposalId": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}

参数

Params 端点允许用户查询 gov 模块的全部参数。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Params
示例:
grpcurl -plaintext \
    -d '{"params_type":"voting"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Params
示例输出:
{
  "votingParams": {
    "votingPeriod": "172800s"
  },
  "depositParams": {
    "maxDepositPeriod": "0s"
  },
  "tallyParams": {
    "quorum": "MA==",
    "threshold": "MA==",
    "vetoThreshold": "MA=="
  }
}
使用 v1:
cosmos.gov.v1.Query/Params
示例:
grpcurl -plaintext \
    -d '{"params_type":"voting"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Params
示例输出:
{
  "votingParams": {
    "votingPeriod": "172800s"
  }
}

单笔押金

Deposit 端点允许用户查询指定提案中指定存款人的一笔押金记录。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Deposit
示例:
grpcurl -plaintext \
    '{"proposal_id":"1","depositor":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Deposit
示例输出:
{
  "deposit": {
    "proposalId": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}
使用 v1:
cosmos.gov.v1.Query/Deposit
示例:
grpcurl -plaintext \
    '{"proposal_id":"1","depositor":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Deposit
示例输出:
{
  "deposit": {
    "proposalId": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}

押金列表

Deposits 端点允许用户查询指定提案的全部押金记录。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/Deposits
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Deposits
示例输出:
{
  "deposits": [
    {
      "proposalId": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}
使用 v1:
cosmos.gov.v1.Query/Deposits
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Deposits
示例输出:
{
  "deposits": [
    {
      "proposalId": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}

计票结果

TallyResult 端点允许用户查询指定提案的计票结果。 使用旧版 v1beta1:
cosmos.gov.v1beta1.Query/TallyResult
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/TallyResult
示例输出:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "noWithVeto": "0"
  }
}
使用 v1:
cosmos.gov.v1.Query/TallyResult
示例:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/TallyResult
示例输出:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "noWithVeto": "0"
  }
}

REST

用户可以通过 REST 端点查询 gov 模块。

提案

proposals 端点允许用户查询指定提案。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1
示例输出:
{
  "proposal": {
    "proposal_id": "1",
    "content": null,
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "final_tally_result": {
      "yes": "0",
      "abstain": "0",
      "no": "0",
      "no_with_veto": "0"
    },
    "submit_time": "2022-03-28T11:50:20.819676256Z",
    "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
    "total_deposit": [
      {
        "denom": "stake",
        "amount": "10000000010"
      }
    ],
    "voting_start_time": "2022-03-28T14:25:26.644857113Z",
    "voting_end_time": "2022-03-30T14:25:26.644857113Z"
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1
示例输出:
{
  "proposal": {
    "id": "1",
    "messages": [
      {
        "@type": "/cosmos.bank.v1beta1.MsgSend",
        "from_address": "cosmos1..",
        "to_address": "cosmos1..",
        "amount": [
          {
            "denom": "stake",
            "amount": "10"
          }
        ]
      }
    ],
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "final_tally_result": {
      "yes_count": "0",
      "abstain_count": "0",
      "no_count": "0",
      "no_with_veto_count": "0"
    },
    "submit_time": "2022-03-28T11:50:20.819676256Z",
    "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
    "total_deposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "voting_start_time": "2022-03-28T14:25:26.644857113Z",
    "voting_end_time": "2022-03-30T14:25:26.644857113Z",
    "metadata": "AQ==",
    "title": "Proposal Title",
    "summary": "Proposal Summary"
  }
}

提案列表

proposals 端点还允许用户在可选过滤条件下查询全部提案。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals
示例输出:
{
  "proposals": [
    {
      "proposal_id": "1",
      "content": null,
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "final_tally_result": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "no_with_veto": "0"
      },
      "submit_time": "2022-03-28T11:50:20.819676256Z",
      "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ],
      "voting_start_time": "2022-03-28T14:25:26.644857113Z",
      "voting_end_time": "2022-03-30T14:25:26.644857113Z"
    },
    {
      "proposal_id": "2",
      "content": null,
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "final_tally_result": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "no_with_veto": "0"
      },
      "submit_time": "2022-03-28T14:02:41.165025015Z",
      "deposit_end_time": "2022-03-30T14:02:41.165025015Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "voting_start_time": "0001-01-01T00:00:00Z",
      "voting_end_time": "0001-01-01T00:00:00Z"
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "2"
  }
}
使用 v1:
/cosmos/gov/v1/proposals
示例:
curl localhost:1317/cosmos/gov/v1/proposals
示例输出:
{
  "proposals": [
    {
      "id": "1",
      "messages": [
        {
          "@type": "/cosmos.bank.v1beta1.MsgSend",
          "from_address": "cosmos1..",
          "to_address": "cosmos1..",
          "amount": [
            {
              "denom": "stake",
              "amount": "10"
            }
          ]
        }
      ],
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "final_tally_result": {
        "yes_count": "0",
        "abstain_count": "0",
        "no_count": "0",
        "no_with_veto_count": "0"
      },
      "submit_time": "2022-03-28T11:50:20.819676256Z",
      "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "voting_start_time": "2022-03-28T14:25:26.644857113Z",
      "voting_end_time": "2022-03-30T14:25:26.644857113Z",
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    },
    {
      "id": "2",
      "messages": [
        {
          "@type": "/cosmos.bank.v1beta1.MsgSend",
          "from_address": "cosmos1..",
          "to_address": "cosmos1..",
          "amount": [
            {
              "denom": "stake",
              "amount": "10"
            }
          ]
        }
      ],
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "final_tally_result": {
        "yes_count": "0",
        "abstain_count": "0",
        "no_count": "0",
        "no_with_veto_count": "0"
      },
      "submit_time": "2022-03-28T14:02:41.165025015Z",
      "deposit_end_time": "2022-03-30T14:02:41.165025015Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "voting_start_time": null,
      "voting_end_time": null,
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "2"
  }
}

投票者投票

votes 端点允许用户查询某个给定提案的某一条投票记录。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes/cosmos1..
示例输出:
{
  "vote": {
    "proposal_id": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ]
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1/votes/cosmos1..
示例输出:
{
  "vote": {
    "proposal_id": "1",
    "voter": "cosmos1..",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ],
    "metadata": ""
  }
}

投票列表

votes 端点允许用户查询某个给定提案的全部投票记录。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes
示例输出:
{
  "votes": [
    {
      "proposal_id": "1",
      "voter": "cosmos1..",
      "option": "VOTE_OPTION_YES",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}/votes
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1/votes
示例输出:
{
  "votes": [
    {
      "proposal_id": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ],
      "metadata": ""
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}

参数

params 端点允许用户查询 gov 模块的全部参数。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/params/{params_type}
示例:
curl localhost:1317/cosmos/gov/v1beta1/params/voting
示例输出:
{
  "voting_params": {
    "voting_period": "172800s"
  },
  "deposit_params": {
    "min_deposit": [
    ],
    "max_deposit_period": "0s"
  },
  "tally_params": {
    "quorum": "0.000000000000000000",
    "threshold": "0.000000000000000000",
    "veto_threshold": "0.000000000000000000"
  }
}
使用 v1:
/cosmos/gov/v1/params/{params_type}
示例:
curl localhost:1317/cosmos/gov/v1/params/voting
示例输出:
{
  "voting_params": {
    "voting_period": "172800s"
  },
  "deposit_params": {
    "min_deposit": [
    ],
    "max_deposit_period": "0s"
  },
  "tally_params": {
    "quorum": "0.000000000000000000",
    "threshold": "0.000000000000000000",
    "veto_threshold": "0.000000000000000000"
  }
}

押金

deposits 端点允许用户查询某个给定提案中某个指定存入者的押金记录。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits/{depositor}
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits/cosmos1..
示例输出:
{
  "deposit": {
    "proposal_id": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}/deposits/{depositor}
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits/cosmos1..
示例输出:
{
  "deposit": {
    "proposal_id": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}

提案押金

deposits 端点允许用户查询某个给定提案的全部押金记录。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits
示例输出:
{
  "deposits": [
    {
      "proposal_id": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}/deposits
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits
示例输出:
{
  "deposits": [
    {
      "proposal_id": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}

计票结果

tally 端点允许用户查询某个给定提案的计票结果。 使用旧版 v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/tally
示例:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/tally
示例输出:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "no_with_veto": "0"
  }
}
使用 v1:
/cosmos/gov/v1/proposals/{proposal_id}/tally
示例:
curl localhost:1317/cosmos/gov/v1/proposals/1/tally
示例输出:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "no_with_veto": "0"
  }
}

元数据

gov 模块有两个可存放元数据的位置,用户可以在这些位置为其执行的链上操作提供更多上下文。默认情况下,所有元数据字段的长度限制均为 255 个字符,可按所需数据量以 JSON 格式存储元数据,既可以存储在链上,也可以存储在链下。这里我们给出推荐的 JSON 结构以及数据存储位置。这些建议主要基于两个重要因素。第一,gov 模块和 group 模块应彼此保持一致,需要注意的是,所有 group 产生的提案数量可能相当大。第二,区块浏览器和治理界面等客户端应用需要对跨链元数据结构的一致性有足够信心。

提案

位置:链下,以存储在 IPFS 上的 JSON 对象形式保存(与组提案一致)
{
  "title": "",
  "authors": [""],
  "summary": "",
  "details": "",
  "proposal_forum_url": "",
  "vote_option_context": "",
}
authors 字段是字符串数组,这样可以在元数据中列出多个作者。 在 v0.46 中,authors 字段是一个逗号分隔的字符串。建议前端同时支持这两种格式,以保持向后兼容。

投票

位置:链上,以 JSON 形式存储,并受 255 字符限制(与组投票一致)
{
  "justification": "",
}

未来改进

当前文档仅描述了治理模块的最小可行产品。未来的改进可能包括:
  • BountyProposals: 如果被接受,BountyProposal 会创建一个开放的赏金任务。BountyProposal 会指定完成后可获得多少 Atoms。这些 Atoms 将从 reserve pool 中提取。在治理接受某个 BountyProposal 后,任何人都可以提交包含代码的 SoftwareUpgradeProposal 来领取赏金。需要注意的是,一旦 BountyProposal 被接受,reserve pool 中对应的资金就会被锁定,从而确保始终能够履行支付。为了将某个 SoftwareUpgradeProposal 关联到一个开放的赏金任务,SoftwareUpgradeProposal 的提交者将使用 Proposal.LinkedProposal 属性。如果某个关联到开放赏金任务的 SoftwareUpgradeProposal 被治理接受,预留的资金将自动转给提交者。
  • 复杂委托: 委托人可以选择除其验证人之外的其他代表。最终,代表链条仍会落到某个验证人,但委托人可以先继承其所选代表的投票,然后才继承其验证人的投票。换句话说,只有在其指定的其他代表未投票时,他们才会继承其验证人的投票。
  • 更完善的提案审核流程: proposal.Deposit 将包含两部分,一部分用于防垃圾提案(与 MVP 相同),另一部分用于奖励第三方审计人员。

Abstract

This paper specifies the Governance module of the Cosmos SDK, which was first described in the Cosmos Whitepaper in June 2016. The module enables Cosmos SDK based blockchain to support an on-chain governance system. In this system, holders of the native staking token of the chain can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports:
  • Proposal submission: Users can submit proposals with a deposit. Once the minimum deposit is reached, the proposal enters voting period. The minimum deposit can be reached by collecting deposits from different users (including proposer) within deposit period.
  • Vote: Participants can vote on proposals that reached MinDeposit and entered voting period.
  • Inheritance and penalties: Delegators inherit their validator’s vote if they don’t vote themselves.
  • Claiming deposit: Users that deposited on proposals can recover their deposits if the proposal was accepted or rejected. If the proposal was vetoed, or never entered voting period (minimum deposit not reached within deposit period), the deposit is burned.
This module is in use on the Cosmos Hub (a.k.a gaia). Features that may be added in the future are described in Future Improvements.

Contents

The following specification uses ATOM as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing ATOM with the native staking token of the chain.

Concepts

The governance process is divided in a few steps that are outlined below:
  • Proposal submission: Proposal is submitted to the blockchain with a deposit.
  • Vote: Once deposit reaches a certain value (MinDeposit), proposal is confirmed and vote opens. Bonded Atom holders can then send TxGovVote transactions to vote on the proposal.
  • Execution After a period of time, the votes are tallied and depending on the result, the messages in the proposal will be executed.

Proposal submission

Right to submit a proposal

Every account can submit proposals by sending a MsgSubmitProposal transaction. Once a proposal is submitted, it is identified by its unique proposalID.

Proposal Messages

A proposal includes an array of sdk.Msgs which are executed automatically if the proposal passes. The messages are executed by the governance ModuleAccount itself. Modules such as x/upgrade, that want to allow certain messages to be executed by governance only should add a whitelist within the respective msg server, granting the governance module the right to execute the message once a quorum has been reached. The governance module uses the MsgServiceRouter to check that these messages are correctly constructed and have a respective path to execute on but do not perform a full validity check.

Deposit

To prevent spam, proposals must be submitted with a deposit in the coins defined by the MinDeposit param. When a proposal is submitted, it has to be accompanied with a deposit that must be strictly positive, but can be inferior to MinDeposit. The submitter doesn’t need to pay for the entire deposit on their own. The newly created proposal is stored in an inactive proposal queue and stays there until its deposit passes the MinDeposit. Other token holders can increase the proposal’s deposit by sending a Deposit transaction. If a proposal doesn’t pass the MinDeposit before the deposit end time (the time when deposits are no longer accepted), the proposal will be destroyed: the proposal will be removed from state and the deposit will be burned (see x/gov EndBlocker). When a proposal deposit passes the MinDeposit threshold (even during the proposal submission) before the deposit end time, the proposal will be moved into the active proposal queue and the voting period will begin. The deposit is kept in escrow and held by the governance ModuleAccount until the proposal is finalized (passed or rejected).

Deposit refund and burn

When a proposal is finalized, the coins from the deposit are either refunded or burned according to the final tally of the proposal:
  • If the proposal is approved or rejected but not vetoed, each deposit will be automatically refunded to its respective depositor (transferred from the governance ModuleAccount).
  • When the proposal is vetoed with greater than 1/3, deposits will be burned from the governance ModuleAccount and the proposal information along with its deposit information will be removed from state.
  • All refunded or burned deposits are removed from the state. Events are issued when burning or refunding a deposit.

Vote

Participants

Participants are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. Note that when participants have bonded and unbonded Atoms, their voting power is calculated from their bonded Atom holdings only.

Voting period

Once a proposal reaches MinDeposit, it immediately enters Voting period. We define Voting period as the interval between the moment the vote opens and the moment the vote closes. The initial value of Voting period is 2 weeks.

Option set

The option set of a proposal refers to the set of choices a participant can choose from when casting its vote. The initial option set includes the following options:
  • Yes
  • No
  • NoWithVeto
  • Abstain
NoWithVeto counts as No but also adds a Veto vote. Abstain option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a NoWithVeto vote.

Weighted Votes

ADR-037 introduces the weighted vote feature which allows a staker to split their votes into several voting options. For example, it could use 70% of its voting power to vote Yes and 30% of its voting power to vote No. Often times the entity owning that address might not be a single individual. For example, a company might have different stakeholders who want to vote differently, and so it makes sense to allow them to split their voting power. Currently, it is not possible for them to do “passthrough voting” and giving their users voting rights over their tokens. However, with this system, exchanges can poll their users for voting preferences, and then vote on-chain proportionally to the results of the poll. To represent weighted vote on chain, we use the following Protobuf message.
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L34-L47
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L181-L201
For a weighted vote to be valid, the options field must not contain duplicate vote options, and the sum of weights of all options must be equal to 1.

Custom Vote Calculation

Cosmos SDK v0.53.0 introduced an option for developers to define a custom vote result and voting power calculation function. As of v0.54, x/gov has been decoupled from x/staking: the keeper.NewKeeper constructor now requires a CalculateVoteResultsAndVotingPowerFn as a required parameter instead of a StakingKeeper. To use the default staking-based tally logic, wrap your staking keeper with keeper.NewDefaultCalculateVoteResultsAndVotingPower(stakingKeeper).
package keeper

import (
    
	"context"
    "fmt"
    "cosmossdk.io/collections"
    "cosmossdk.io/math"

	sdk "github.com/cosmos/cosmos-sdk/types"
	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// CalculateVoteResultsAndVotingPowerFn is a function signature for calculating vote results and voting power
// It can be overridden to customize the voting power calculation for proposals
// It gets the proposal tallied and the validators governance infos (validator power, voting power, etc.)
// It must return the total voting power and the results of the vote
type CalculateVoteResultsAndVotingPowerFn func(
	ctx context.Context,
	k Keeper,
	proposal v1.Proposal,
	validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error)

func defaultCalculateVoteResultsAndVotingPower(
	ctx context.Context,
	k Keeper,
	proposal v1.Proposal,
	validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) {
    totalVotingPower := math.LegacyZeroDec()

results = make(map[v1.VoteOption]math.LegacyDec)

results[v1.OptionYes] = math.LegacyZeroDec()

results[v1.OptionAbstain] = math.LegacyZeroDec()

results[v1.OptionNo] = math.LegacyZeroDec()

results[v1.OptionNoWithVeto] = math.LegacyZeroDec()
    rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
    votesToRemove := []collections.Pair[uint64, sdk.AccAddress]{
}

err = k.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) {
		// if validator, just record it in the map
		voter, err := k.authKeeper.AddressCodec().StringToBytes(vote.Voter)
    if err != nil {
    return false, err
}

valAddrStr, err := k.sk.ValidatorAddressCodec().BytesToString(voter)
    if err != nil {
    return false, err
}
    if val, ok := validators[valAddrStr]; ok {
    val.Vote = vote.Options
			validators[valAddrStr] = val
}

		// iterate over all delegations from voter, deduct from any delegated-to validators
		err = k.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) {
    valAddrStr := delegation.GetValidatorAddr()
    if val, ok := validators[valAddrStr]; ok {
				// There is no need to handle the special case that validator address equal to voter address.
				// Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator.
				val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares())

validators[valAddrStr] = val

				// delegation shares * bonded / total shares
    votingPower := delegation.GetShares().MulInt(val.ValidatorPower).Quo(val.DelegatorShares)
    for _, option := range vote.Options {
    weight, _ := math.LegacyNewDecFromStr(option.Weight)
    subPower := votingPower.Mul(weight)

results[option.Option] = results[option.Option].Add(subPower)
}

totalVotingPower = totalVotingPower.Add(votingPower)
}

return false
})
    if err != nil {
    return false, err
}

votesToRemove = append(votesToRemove, key)

return false, nil
})
    if err != nil {
    return math.LegacyZeroDec(), nil, fmt.Errorf("error while iterating delegations: %w", err)
}

	// remove all votes from store
    for _, key := range votesToRemove {
    if err := k.Votes.Remove(ctx, key); err != nil {
    return math.LegacyDec{
}, nil, fmt.Errorf("error while removing vote (%d/%s): %w", key.K1(), key.K2(), err)
}
	
}

	// iterate over the validators again to tally their voting power
    for _, val := range validators {
    if len(val.Vote) == 0 {
    continue
}
    sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions)
    votingPower := sharesAfterDeductions.MulInt(val.ValidatorPower).Quo(val.DelegatorShares)
    for _, option := range val.Vote {
    weight, _ := math.LegacyNewDecFromStr(option.Weight)
    subPower := votingPower.Mul(weight)

results[option.Option] = results[option.Option].Add(subPower)
}

totalVotingPower = totalVotingPower.Add(votingPower)
}

return totalVotingPower, results, nil
}

// getCurrentValidators fetches all the bonded validators, insert them into currValidators
func (k Keeper)

getCurrentValidators(ctx context.Context) (map[string]v1.ValidatorGovInfo, error) {
    currValidators := make(map[string]v1.ValidatorGovInfo)
    if err := k.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) {
    valBz, err := k.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
    if err != nil {
    return false
}

currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo(
			valBz,
			validator.GetValidatorPower(),
			validator.GetDelegatorShares(),
			math.LegacyZeroDec(),
			v1.WeightedVoteOptions{
},
		)

return false
}); err != nil {
    return nil, err
}

return currValidators, nil
}

// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the
// voters
func (k Keeper)

Tally(ctx context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) {
    currValidators, err := k.getCurrentValidators(ctx)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while getting current validators: %w", err)
}
    tallyFn := k.calculateVoteResultsAndVotingPowerFn
	totalVotingPower, results, err := tallyFn(ctx, k, proposal, currValidators)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while calculating tally results: %w", err)
}

tallyResults = v1.NewTallyResultFromMap(results)

	// TODO: Upgrade the spec to cover all of these cases & remove pseudocode.
	// If there is no staked coins, the proposal fails
	totalBonded, err := k.sk.TotalValidatorPower(ctx)
    if err != nil {
    return false, false, tallyResults, err
}
    if totalBonded.IsZero() {
    return false, false, tallyResults, nil
}

params, err := k.Params.Get(ctx)
    if err != nil {
    return false, false, tallyResults, fmt.Errorf("error while getting params: %w", err)
}

	// If there is not enough quorum of votes, the proposal fails
    percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded))

quorum, _ := math.LegacyNewDecFromStr(params.Quorum)
    if percentVoting.LT(quorum) {
    return false, params.BurnVoteQuorum, tallyResults, nil
}

	// If no one votes (everyone abstains), proposal fails
    if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) {
    return false, false, tallyResults, nil
}

	// If more than 1/3 of voters veto, proposal fails
	vetoThreshold, _ := math.LegacyNewDecFromStr(params.VetoThreshold)
    if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) {
    return false, params.BurnVoteVeto, tallyResults, nil
}

	// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
	// For expedited 2/3
	var thresholdStr string
    if proposal.Expedited {
    thresholdStr = params.GetExpeditedThreshold()
}

else {
    thresholdStr = params.GetThreshold()
}

threshold, _ := math.LegacyNewDecFromStr(thresholdStr)
    if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) {
    return true, false, tallyResults, nil
}

	// If more than 1/2 of non-abstaining voters vote No, proposal fails
	return false, false, tallyResults, nil
}
This gives developers a more expressive way to handle governance on their appchains. Developers can now build systems with:
  • Quadratic Voting
  • Time-weighted Voting
  • Reputation-Based voting
Example
func myCustomVotingFunction(
  ctx context.Context,
  k Keeper,
  proposal v1.Proposal,
  validators map[string]v1.ValidatorGovInfo,
) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) {
  // ... tally logic
}
    govKeeper := govkeeper.NewKeeper(
  appCodec,
  runtime.NewKVStoreService(keys[govtypes.StoreKey]),
  app.AccountKeeper,
  app.BankKeeper,
  app.DistrKeeper, // optional: can be nil if the module address is not used as a cancellation fee destination
  app.MsgServiceRouter(),
  govConfig,
  authtypes.NewModuleAddress(govtypes.ModuleName).String(),
  myCustomVotingFunction, // required: CalculateVoteResultsAndVotingPowerFn
)

Quorum

Quorum is defined as the minimum percentage of voting power that needs to be cast on a proposal for the result to be valid.

Expedited Proposals

A proposal can be expedited, making the proposal use shorter voting duration and a higher tally threshold by its default. If an expedited proposal fails to meet the threshold within the scope of shorter voting duration, the expedited proposal is then converted to a regular proposal and restarts voting under regular voting conditions.

Threshold

Threshold is defined as the minimum proportion of Yes votes (excluding Abstain votes) for the proposal to be accepted. Initially, the threshold is set at 50% of Yes votes, excluding Abstain votes. A possibility to veto exists if more than 1/3rd of all votes are NoWithVeto votes. Note, both of these values are derived from the TallyParams on-chain parameter, which is modifiable by governance. This means that proposals are accepted iff:
  • There exist bonded tokens.
  • Quorum has been achieved.
  • The proportion of Abstain votes is inferior to 1/1.
  • The proportion of NoWithVeto votes is inferior to 1/3, including Abstain votes.
  • The proportion of Yes votes, excluding Abstain votes, at the end of the voting period is superior to 1/2.
For expedited proposals, by default, the threshold is higher than with a normal proposal, namely, 66.7%.

Inheritance

If a delegator does not vote, it will inherit its validator vote.
  • If the delegator votes before its validator, it will not inherit from the validator’s vote.
  • If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is urgent, it is possible that the vote will close before delegators have a chance to react and override their validator’s vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass, when tallied at the end of the voting period. Because as little as 1/3 + 1 validation power could collude to censor transactions, non-collusion is already assumed for ranges exceeding this threshold.

Validator’s punishment for non-voting

At present, validators are not punished for failing to vote.

Governance address

Later, we may add permissioned keys that could only sign txs from certain modules. For the MVP, the Governance address will be the main validator address generated at account creation. This address corresponds to a different PrivKey than the CometBFT PrivKey which is responsible for signing consensus messages. Validators thus do not have to sign governance transactions with the sensitive CometBFT PrivKey.

Burnable Params

There are three parameters that define if the deposit of a proposal should be burned or returned to the depositors.
  • BurnVoteVeto burns the proposal deposit if the proposal gets vetoed.
  • BurnVoteQuorum burns the proposal deposit if the proposal deposit if the vote does not reach quorum.
  • BurnProposalDepositPrevote burns the proposal deposit if it does not enter the voting phase.
Note: These parameters are modifiable via governance.

State

Constitution

Constitution is found in the genesis state. It is a string field intended to be used to describe the purpose of a particular blockchain, and its expected norms. A few examples of how the constitution field can be used:
  • define the purpose of the chain, laying a foundation for its future development
  • set expectations for delegators
  • set expectations for validators
  • define the chain’s relationship to “meatspace” entities, like a foundation or corporation
Since this is more of a social feature than a technical feature, we’ll now get into some items that may have been useful to have in a genesis constitution:
  • What limitations on governance exist, if any?
    • is it okay for the community to slash the wallet of a whale that they no longer feel that they want around? (viz: Juno Proposal 4 and 16)
    • can governance “socially slash” a validator who is using unapproved MEV? (viz: commonwealth.im/osmosis)
    • In the event of an economic emergency, what should validators do?
      • Terra crash of May, 2022, saw validators choose to run a new binary with code that had not been approved by governance, because the governance token had been inflated to nothing.
  • What is the purpose of the chain, specifically?
    • best example of this is the Cosmos hub, where different founding groups, have different interpertations of the purpose of the network.
This genesis entry, “constitution” hasn’t been designed for existing chains, who should likely just ratify a constitution using their governance system. Instead, this is for new chains. It will allow for validators to have a much clearer idea of purpose and the expectations placed on them while operating their nodes. Likewise, for community members, the constitution will give them some idea of what to expect from both the “chain team” and the validators, respectively. This constitution is designed to be immutable, and placed only in genesis, though that could change over time by a pull request to the cosmos-sdk that allows for the constitution to be changed by governance. Communities wishing to make amendments to their original constitution should use the governance mechanism and a “signaling proposal” to do exactly that. Ideal use scenario for a cosmos chain constitution As a chain developer, you decide that you’d like to provide clarity to your key user groups:
  • validators
  • token holders
  • developers (yourself)
You use the constitution to immutably store some Markdown in genesis, so that when difficult questions come up, the constitution can provide guidance to the community.

Proposals

Proposal objects are used to tally votes and generally track the proposal’s state. They contain an array of arbitrary sdk.Msg’s which the governance module will attempt to resolve and then execute if the proposal passes. Proposal’s are identified by a unique id and contains a series of timestamps: submit_time, deposit_end_time, voting_start_time, voting_end_time which track the lifecycle of a proposal
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L51-L99
A proposal will generally require more than just a set of messages to explain its purpose but need some greater justification and allow a means for interested participants to discuss and debate the proposal. In most cases, it is encouraged to have an off-chain system that supports the on-chain governance process. To accommodate for this, a proposal contains a special metadata field, a string, which can be used to add context to the proposal. The metadata field allows custom use for networks, however, it is expected that the field contains a URL or some form of CID using a system such as IPFS. To support the case of interoperability across networks, the SDK recommends that the metadata represents the following JSON template:
{
  "title": "...",
  "description": "...",
  "forum": "...", // a link to the discussion platform (i.e. Discord)
  "other": "..." // any extra data that doesn't correspond to the other fields
}
This makes it far easier for clients to support multiple networks. The metadata has a maximum length that is chosen by the app developer, and passed into the gov keeper as a config. The default maximum length in the SDK is 255 characters.

Writing a module that uses governance

There are many aspects of a chain, or of the individual modules that you may want to use governance to perform such as changing various parameters. This is very simple to do. First, write out your message types and MsgServer implementation. Add an authority field to the keeper which will be populated in the constructor with the governance module account: govKeeper.GetGovernanceAccount().GetAddress(). Then for the methods in the msg_server.go, perform a check on the message that the signer matches authority. This will prevent any user from executing that message.

Parameters and base types

Parameters define the rules according to which votes are run. There can only be one active parameter set at any given time. If governance wants to change a parameter set, either to modify a value or add/remove a parameter field, a new parameter set has to be created and the previous one rendered inactive.

DepositParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L152-L162

VotingParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L164-L168

TallyParams

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L170-L182
Parameters are stored in a global GlobalParams KVStore. Additionally, we introduce some basic types:
type Vote byte

const (
    VoteYes         = 0x1
    VoteNo          = 0x2
    VoteNoWithVeto  = 0x3
    VoteAbstain     = 0x4
)

type ProposalType  string

const (
    ProposalTypePlainText       = "Text"
    ProposalTypeSoftwareUpgrade = "SoftwareUpgrade"
)

type ProposalStatus byte

const (
    StatusNil           ProposalStatus = 0x00
    StatusDepositPeriod ProposalStatus = 0x01  // Proposal is submitted. Participants can deposit on it but not vote
    StatusVotingPeriod  ProposalStatus = 0x02  // MinDeposit is reached, participants can vote
    StatusPassed        ProposalStatus = 0x03  // Proposal passed and successfully executed
    StatusRejected      ProposalStatus = 0x04  // Proposal has been rejected
    StatusFailed        ProposalStatus = 0x05  // Proposal passed but failed execution
)

Deposit

// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L38-L49

ValidatorGovInfo

This type is used in a temp map when tallying
type ValidatorGovInfo struct {
    Minus     sdk.Dec
    Vote      Vote
}

Stores

Stores are KVStores in the multi-store. The key to find the store is the first parameter in the list
We will use one KVStore Governance to store four mappings:
  • A mapping from proposalID|'proposal' to Proposal.
  • A mapping from proposalID|'addresses'|address to Vote. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on proposalID:addresses.
  • A mapping from ParamsKey|'Params' to Params. This map allows to query all x/gov params.
  • A mapping from VotingPeriodProposalKeyPrefix|proposalID to a single byte. This allows us to know if a proposal is in the voting period or not with very low gas cost.
For pseudocode purposes, here are the two function we will use to read or write in stores:
  • load(StoreKey, Key): Retrieve item stored at key Key in store found at key StoreKey in the multistore
  • store(StoreKey, Key, value): Write value Value at key Key in store found at key StoreKey in the multistore

Proposal Processing Queue

Store:
  • ProposalProcessingQueue: A queue queue[proposalID] containing all the ProposalIDs of proposals that reached MinDeposit. During each EndBlock, all the proposals that have reached the end of their voting period are processed. To process a finished proposal, the application tallies the votes, computes the votes of each validator and checks if every validator in the validator set has voted. If the proposal is accepted, deposits are refunded. Finally, the proposal content Handler is executed.
And the pseudocode for the ProposalProcessingQueue:
in EndBlock do
    for finishedProposalID in GetAllFinishedProposalIDs(block.Time)

proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key

      validators = Keeper.getAllValidators()
    tmpValMap := map(sdk.AccAddress)

ValidatorGovInfo

      // Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes
    for each validator in validators
        tmpValMap(validator.OperatorAddr).Minus = 0

      // Tally
      voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
    for each (voterAddress, vote)

in voterIterator
        delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter
    for each delegation in delegations
          // make sure delegation.Shares does NOT include shares being unbonded
          tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
          proposal.updateTally(vote, delegation.Shares)

        _, isVal = stakingKeeper.getValidator(voterAddress)
    if (isVal)

tmpValMap(voterAddress).Vote = vote

      tallyingParam = load(GlobalParams, 'TallyingParam')

      // Update tally if validator voted
    for each validator in validators
    if tmpValMap(validator).HasVoted
          proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))

      // Check if proposal is accepted or rejected
    totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
    if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain  < tallyingParam.Veto)
        //  proposal was accepted at the end of the voting period
        //  refund deposits (non-voters already punished)
    for each (amount, depositor)

in proposal.Deposits
          depositor.AtomBalance += amount

        stateWriter, err := proposal.Handler()
    if err != nil
            // proposal passed but failed during state execution
            proposal.CurrentStatus = ProposalStatusFailed
         else
            // proposal pass and state is persisted
            proposal.CurrentStatus = ProposalStatusAccepted
            stateWriter.save()

else
        // proposal was rejected
        proposal.CurrentStatus = ProposalStatusRejected

      store(Governance, <proposalID|'proposal'>, proposal)

Legacy Proposal

Legacy proposals are deprecated. Use the new proposal flow by granting the governance module the right to execute the message.
A legacy proposal is the old implementation of governance proposal. Contrary to proposal that can contain any messages, a legacy proposal allows to submit a set of pre-defined proposals. These proposals are defined by their types and handled by handlers that are registered in the gov v1beta1 router. More information on how to submit proposals in the client section.

Messages

Proposal Submission

Proposals can be submitted by any account via a MsgSubmitProposal transaction.
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L42-L69
All sdk.Msgs passed into the messages field of a MsgSubmitProposal message must be registered in the app’s MsgServiceRouter. Each of these messages must have one signer, namely the gov module account. And finally, the metadata length must not be larger than the maxMetadataLen config passed into the gov keeper. The initialDeposit must be strictly positive and conform to the accepted denom of the MinDeposit param. State modifications:
  • Generate new proposalID
  • Create new Proposal
  • Initialize Proposal’s attributes
  • Decrease balance of sender by InitialDeposit
  • If MinDeposit is reached:
    • Push proposalID in ProposalProcessingQueue
  • Transfer InitialDeposit from the Proposer to the governance ModuleAccount

Deposit

Once a proposal is submitted, if Proposal.TotalDeposit < ActiveParam.MinDeposit, Atom holders can send MsgDeposit transactions to increase the proposal’s deposit. A deposit is accepted iff:
  • The proposal exists
  • The proposal is not in the voting period
  • The deposited coins are conform to the accepted denom from the MinDeposit param
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L134-L147
State modifications:
  • Decrease balance of sender by deposit
  • Add deposit of sender in proposal.Deposits
  • Increase proposal.TotalDeposit by sender’s deposit
  • If MinDeposit is reached:
    • Push proposalID in ProposalProcessingQueueEnd
  • Transfer Deposit from the proposer to the governance ModuleAccount

Vote

Once ActiveParam.MinDeposit is reached, voting period starts. From there, bonded Atom holders are able to send MsgVote transactions to cast their vote on the proposal.
// Reference: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L92-L108
State modifications:
  • Record Vote of sender
Gas cost for this message has to take into account the future tallying of the vote in EndBlocker.

Events

The governance module emits the following events:

EndBlocker

TypeAttribute KeyAttribute Value
inactive_proposalproposal_id{proposalID}
inactive_proposalproposal_result{proposalResult}
active_proposalproposal_id{proposalID}
active_proposalproposal_result{proposalResult}

Handlers

MsgSubmitProposal

TypeAttribute KeyAttribute Value
submit_proposalproposal_id{proposalID}
submit_proposal [0]voting_period_start{proposalID}
proposal_depositamount{depositAmount}
proposal_depositproposal_id{proposalID}
messagemodulegovernance
messageactionsubmit_proposal
messagesender{senderAddress}
  • [0] Event only emitted if the voting period starts during the submission.

MsgVote

TypeAttribute KeyAttribute Value
proposal_voteoption{voteOption}
proposal_voteproposal_id{proposalID}
messagemodulegovernance
messageactionvote
messagesender{senderAddress}

MsgVoteWeighted

TypeAttribute KeyAttribute Value
proposal_voteoption{weightedVoteOptions}
proposal_voteproposal_id{proposalID}
messagemodulegovernance
messageactionvote
messagesender{senderAddress}

MsgDeposit

TypeAttribute KeyAttribute Value
proposal_depositamount{depositAmount}
proposal_depositproposal_id{proposalID}
proposal_deposit [0]voting_period_start{proposalID}
messagemodulegovernance
messageactiondeposit
messagesender{senderAddress}
  • [0] Event only emitted if the voting period starts during the submission.

Hooks

The governance module exposes a GovHooks interface that allows other modules to react to governance events.
type GovHooks interface {
    AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error
    AfterProposalDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress) error
    AfterProposalVote(ctx context.Context, proposalID uint64, voterAddr sdk.AccAddress) error
    AfterProposalFailedMinDeposit(ctx context.Context, proposalID uint64) error
    AfterProposalVotingPeriodEnded(ctx context.Context, proposalID uint64) error
}

AfterProposalSubmission

Called after a proposal is submitted. The hook receives the proposal ID and the proposer’s address. Note: The proposerAddr parameter was added in a recent release. If you are implementing GovHooks, you must update your AfterProposalSubmission method signature to include proposerAddr sdk.AccAddress as a third parameter. Before:
func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64) error {
    // implementation
}
After:
func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error {
    // implementation
}

AfterProposalDeposit

Called after a deposit is made on a proposal.

AfterProposalVote

Called after a vote is cast on a proposal.

AfterProposalFailedMinDeposit

Called when a proposal fails to reach the minimum deposit within the deposit period.

AfterProposalVotingPeriodEnded

Called when a proposal’s voting period ends.

Parameters

The governance module contains the following parameters:
KeyTypeExample
min_depositarray (coins)[{"denom":"uatom","amount":"10000000"}]
max_deposit_periodstring (time ns)“172800000000000” (17280s)
voting_periodstring (time ns)“172800000000000” (17280s)
quorumstring (dec)“0.334000000000000000”
thresholdstring (dec)“0.500000000000000000”
vetostring (dec)“0.334000000000000000”
expedited_thresholdstring (time ns)“0.667000000000000000”
expedited_voting_periodstring (time ns)“86400000000000” (8600s)
expedited_min_depositarray (coins)[{"denom":"uatom","amount":"50000000"}]
burn_proposal_deposit_prevoteboolfalse
burn_vote_quorumboolfalse
burn_vote_vetobooltrue
min_initial_deposit_ratiostring”0.1”
NOTE: The governance module contains parameters that are objects unlike other modules. If only a subset of parameters are desired to be changed, only they need to be included and not the entire parameter object structure.

Client

CLI

A user can query and interact with the gov module using the CLI.

Query

The query commands allow users to query gov state.
simd query gov --help
deposit
The deposit command allows users to query a deposit for a given proposal from a given depositor.
simd query gov deposit [proposal-id] [depositer-addr] [flags]
Example:
simd query gov deposit 1 cosmos1..
Example Output:
amount:
- amount: "100"
  denom: stake
depositor: cosmos1..
proposal_id: "1"
deposits
The deposits command allows users to query all deposits for a given proposal.
simd query gov deposits [proposal-id] [flags]
Example:
simd query gov deposits 1
Example Output:
deposits:
- amount:
  - amount: "100"
    denom: stake
  depositor: cosmos1..
  proposal_id: "1"
pagination:
  next_key: null
  total: "0"
param
The param command allows users to query a given parameter for the gov module.
simd query gov param [param-type] [flags]
Example:
simd query gov param voting
Example Output:
voting_period: "172800000000000"
params
The params command allows users to query all parameters for the gov module.
simd query gov params [flags]
Example:
simd query gov params
Example Output:
deposit_params:
  max_deposit_period: 172800s
  min_deposit:
  - amount: "10000000"
    denom: stake
params:
  expedited_min_deposit:
  - amount: "50000000"
    denom: stake
  expedited_threshold: "0.670000000000000000"
  expedited_voting_period: 86400s
  max_deposit_period: 172800s
  min_deposit:
  - amount: "10000000"
    denom: stake
  min_initial_deposit_ratio: "0.000000000000000000"
  proposal_cancel_burn_rate: "0.500000000000000000"
  quorum: "0.334000000000000000"
  threshold: "0.500000000000000000"
  veto_threshold: "0.334000000000000000"
  voting_period: 172800s
tally_params:
  quorum: "0.334000000000000000"
  threshold: "0.500000000000000000"
  veto_threshold: "0.334000000000000000"
voting_params:
  voting_period: 172800s
proposal
The proposal command allows users to query a given proposal.
simd query gov proposal [proposal-id] [flags]
Example:
simd query gov proposal 1
Example Output:
deposit_end_time: "2022-03-30T11:50:20.819676256Z"
final_tally_result:
  abstain_count: "0"
  no_count: "0"
  no_with_veto_count: "0"
  yes_count: "0"
id: "1"
messages:
- '@type': /cosmos.bank.v1beta1.MsgSend
  amount:
  - amount: "10"
    denom: stake
  from_address: cosmos1..
  to_address: cosmos1..
metadata: AQ==
status: PROPOSAL_STATUS_DEPOSIT_PERIOD
submit_time: "2022-03-28T11:50:20.819676256Z"
total_deposit:
- amount: "10"
  denom: stake
voting_end_time: null
voting_start_time: null
proposals
The proposals command allows users to query all proposals with optional filters.
simd query gov proposals [flags]
Example:
simd query gov proposals
Example Output:
pagination:
  next_key: null
  total: "0"
proposals:
- deposit_end_time: "2022-03-30T11:50:20.819676256Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "1"
  messages:
  - '@type': /cosmos.bank.v1beta1.MsgSend
    amount:
    - amount: "10"
      denom: stake
    from_address: cosmos1..
    to_address: cosmos1..
  metadata: AQ==
  status: PROPOSAL_STATUS_DEPOSIT_PERIOD
  submit_time: "2022-03-28T11:50:20.819676256Z"
  total_deposit:
  - amount: "10"
    denom: stake
  voting_end_time: null
  voting_start_time: null
- deposit_end_time: "2022-03-30T14:02:41.165025015Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "2"
  messages:
  - '@type': /cosmos.bank.v1beta1.MsgSend
    amount:
    - amount: "10"
      denom: stake
    from_address: cosmos1..
    to_address: cosmos1..
  metadata: AQ==
  status: PROPOSAL_STATUS_DEPOSIT_PERIOD
  submit_time: "2022-03-28T14:02:41.165025015Z"
  total_deposit:
  - amount: "10"
    denom: stake
  voting_end_time: null
  voting_start_time: null
proposer
The proposer command allows users to query the proposer for a given proposal.
simd query gov proposer [proposal-id] [flags]
Example:
simd query gov proposer 1
Example Output:
proposal_id: "1"
proposer: cosmos1..
tally
The tally command allows users to query the tally of a given proposal vote.
simd query gov tally [proposal-id] [flags]
Example:
simd query gov tally 1
Example Output:
abstain: "0"
"no": "0"
no_with_veto: "0"
"yes": "1"
vote
The vote command allows users to query a vote for a given proposal.
simd query gov vote [proposal-id] [voter-addr] [flags]
Example:
simd query gov vote 1 cosmos1..
Example Output:
option: VOTE_OPTION_YES
options:
- option: VOTE_OPTION_YES
  weight: "1.000000000000000000"
proposal_id: "1"
voter: cosmos1..
votes
The votes command allows users to query all votes for a given proposal.
simd query gov votes [proposal-id] [flags]
Example:
simd query gov votes 1
Example Output:
pagination:
  next_key: null
  total: "0"
votes:
- option: VOTE_OPTION_YES
  options:
  - option: VOTE_OPTION_YES
    weight: "1.000000000000000000"
  proposal_id: "1"
  voter: cosmos1..

Transactions

The tx commands allow users to interact with the gov module.
simd tx gov --help
deposit
The deposit command allows users to deposit tokens for a given proposal.
simd tx gov deposit [proposal-id] [deposit] [flags]
Example:
simd tx gov deposit 1 10000000stake --from cosmos1..
draft-proposal
The draft-proposal command allows users to draft any type of proposal. The command returns a draft_proposal.json, to be used by submit-proposal after being completed. The draft_metadata.json is meant to be uploaded to IPFS.
simd tx gov draft-proposal
submit-proposal
The submit-proposal command allows users to submit a governance proposal along with some messages and metadata. Messages, metadata and deposit are defined in a JSON file.
simd tx gov submit-proposal [path-to-proposal-json] [flags]
Example:
simd tx gov submit-proposal /path/to/proposal.json --from cosmos1..
where proposal.json contains:
{
  "messages": [
    {
  "@type": "/cosmos.bank.v1beta1.MsgSend",
  "from_address": "cosmos1...", // The gov module module address
      "to_address": "cosmos1...",
  "amount":[{
  "denom": "stake",
  "amount": "10"}]
    }
  ],
  "metadata": "AQ==",
  "deposit": "10stake",
  "title": "Proposal Title",
  "summary": "Proposal Summary"
}
By default the metadata, summary and title are both limited by 255 characters, this can be overridden by the application developer.
When metadata is not specified, the title is limited to 255 characters and the summary 40x the title length.
submit-legacy-proposal
The submit-legacy-proposal command allows users to submit a governance legacy proposal along with an initial deposit.
simd tx gov submit-legacy-proposal [command] [flags]
Example:
simd tx gov submit-legacy-proposal --title="Test Proposal" --description="testing" --type="Text" --deposit="100000000stake" --from cosmos1..
Example (param-change):
simd tx gov submit-legacy-proposal param-change proposal.json --from cosmos1..
{
  "title": "Test Proposal",
  "description": "testing, testing, 1, 2, 3",
  "changes": [
    {
      "subspace": "staking",
      "key": "MaxValidators",
      "value": 100
    }
  ],
  "deposit": "10000000stake"
}

cancel-proposal

Once proposal is canceled, from the deposits of proposal deposits * proposal_cancel_ratio will be burned or sent to ProposalCancelDest address , if ProposalCancelDest is empty then deposits will be burned. The remaining deposits will be sent to depositers.
simd tx gov cancel-proposal [proposal-id] [flags]
Example:
simd tx gov cancel-proposal 1 --from cosmos1...
vote
The vote command allows users to submit a vote for a given governance proposal.
simd tx gov vote [command] [flags]
Example:
simd tx gov vote 1 yes --from cosmos1..
weighted-vote
The weighted-vote command allows users to submit a weighted vote for a given governance proposal.
simd tx gov weighted-vote [proposal-id] [weighted-options] [flags]
Example:
simd tx gov weighted-vote 1 yes=0.5,no=0.5 --from cosmos1..

gRPC

A user can query the gov module using gRPC endpoints.

Proposal

The Proposal endpoint allows users to query a given proposal. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Proposal
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Proposal
Example Output:
{
  "proposal": {
    "proposalId": "1",
    "content": {"@type":"/cosmos.gov.v1beta1.TextProposal","description":"testing, testing, 1, 2, 3","title":"Test Proposal"},
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "finalTallyResult": {
      "yes": "0",
      "abstain": "0",
      "no": "0",
      "noWithVeto": "0"
    },
    "submitTime": "2021-09-16T19:40:08.712440474Z",
    "depositEndTime": "2021-09-18T19:40:08.712440474Z",
    "totalDeposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "votingStartTime": "2021-09-16T19:40:08.712440474Z",
    "votingEndTime": "2021-09-18T19:40:08.712440474Z",
    "title": "Test Proposal",
    "summary": "testing, testing, 1, 2, 3"
  }
}
Using v1:
cosmos.gov.v1.Query/Proposal
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Proposal
Example Output:
{
  "proposal": {
    "id": "1",
    "messages": [
      {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
    ],
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "finalTallyResult": {
      "yesCount": "0",
      "abstainCount": "0",
      "noCount": "0",
      "noWithVetoCount": "0"
    },
    "submitTime": "2022-03-28T11:50:20.819676256Z",
    "depositEndTime": "2022-03-30T11:50:20.819676256Z",
    "totalDeposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "votingStartTime": "2022-03-28T14:25:26.644857113Z",
    "votingEndTime": "2022-03-30T14:25:26.644857113Z",
    "metadata": "AQ==",
    "title": "Test Proposal",
    "summary": "testing, testing, 1, 2, 3"
  }
}

Proposals

The Proposals endpoint allows users to query all proposals with optional filters. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Proposals
Example:
grpcurl -plaintext \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Proposals
Example Output:
{
  "proposals": [
    {
      "proposalId": "1",
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "finalTallyResult": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "noWithVeto": "0"
      },
      "submitTime": "2022-03-28T11:50:20.819676256Z",
      "depositEndTime": "2022-03-30T11:50:20.819676256Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "votingStartTime": "2022-03-28T14:25:26.644857113Z",
      "votingEndTime": "2022-03-30T14:25:26.644857113Z"
    },
    {
      "proposalId": "2",
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "finalTallyResult": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "noWithVeto": "0"
      },
      "submitTime": "2022-03-28T14:02:41.165025015Z",
      "depositEndTime": "2022-03-30T14:02:41.165025015Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "votingStartTime": "0001-01-01T00:00:00Z",
      "votingEndTime": "0001-01-01T00:00:00Z"
    }
  ],
  "pagination": {
    "total": "2"
  }
}

Using v1:
cosmos.gov.v1.Query/Proposals
Example:
grpcurl -plaintext \
    localhost:9090 \
    cosmos.gov.v1.Query/Proposals
Example Output:
{
  "proposals": [
    {
      "id": "1",
      "messages": [
        {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
      ],
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "finalTallyResult": {
        "yesCount": "0",
        "abstainCount": "0",
        "noCount": "0",
        "noWithVetoCount": "0"
      },
      "submitTime": "2022-03-28T11:50:20.819676256Z",
      "depositEndTime": "2022-03-30T11:50:20.819676256Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "votingStartTime": "2022-03-28T14:25:26.644857113Z",
      "votingEndTime": "2022-03-30T14:25:26.644857113Z",
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    },
    {
      "id": "2",
      "messages": [
        {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."}
      ],
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "finalTallyResult": {
        "yesCount": "0",
        "abstainCount": "0",
        "noCount": "0",
        "noWithVetoCount": "0"
      },
      "submitTime": "2022-03-28T14:02:41.165025015Z",
      "depositEndTime": "2022-03-30T14:02:41.165025015Z",
      "totalDeposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    }
  ],
  "pagination": {
    "total": "2"
  }
}

Vote

The Vote endpoint allows users to query a vote for a given proposal. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Vote
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1","voter":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Vote
Example Output:
{
  "vote": {
    "proposalId": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1000000000000000000"
      }
    ]
  }
}
Using v1:
cosmos.gov.v1.Query/Vote
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1","voter":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Vote
Example Output:
{
  "vote": {
    "proposalId": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ]
  }
}

Votes

The Votes endpoint allows users to query all votes for a given proposal. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Votes
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Votes
Example Output:
{
  "votes": [
    {
      "proposalId": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}
Using v1:
cosmos.gov.v1.Query/Votes
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Votes
Example Output:
{
  "votes": [
    {
      "proposalId": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}

Params

The Params endpoint allows users to query all parameters for the gov module. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Params
Example:
grpcurl -plaintext \
    -d '{"params_type":"voting"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Params
Example Output:
{
  "votingParams": {
    "votingPeriod": "172800s"
  },
  "depositParams": {
    "maxDepositPeriod": "0s"
  },
  "tallyParams": {
    "quorum": "MA==",
    "threshold": "MA==",
    "vetoThreshold": "MA=="
  }
}
Using v1:
cosmos.gov.v1.Query/Params
Example:
grpcurl -plaintext \
    -d '{"params_type":"voting"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Params
Example Output:
{
  "votingParams": {
    "votingPeriod": "172800s"
  }
}

Deposit

The Deposit endpoint allows users to query a deposit for a given proposal from a given depositor. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Deposit
Example:
grpcurl -plaintext \
    '{"proposal_id":"1","depositor":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Deposit
Example Output:
{
  "deposit": {
    "proposalId": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}
Using v1:
cosmos.gov.v1.Query/Deposit
Example:
grpcurl -plaintext \
    '{"proposal_id":"1","depositor":"cosmos1.."}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Deposit
Example Output:
{
  "deposit": {
    "proposalId": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}

deposits

The Deposits endpoint allows users to query all deposits for a given proposal. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/Deposits
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/Deposits
Example Output:
{
  "deposits": [
    {
      "proposalId": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}
Using v1:
cosmos.gov.v1.Query/Deposits
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/Deposits
Example Output:
{
  "deposits": [
    {
      "proposalId": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "total": "1"
  }
}

TallyResult

The TallyResult endpoint allows users to query the tally of a given proposal. Using legacy v1beta1:
cosmos.gov.v1beta1.Query/TallyResult
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1beta1.Query/TallyResult
Example Output:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "noWithVeto": "0"
  }
}
Using v1:
cosmos.gov.v1.Query/TallyResult
Example:
grpcurl -plaintext \
    -d '{"proposal_id":"1"}' \
    localhost:9090 \
    cosmos.gov.v1.Query/TallyResult
Example Output:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "noWithVeto": "0"
  }
}

REST

A user can query the gov module using REST endpoints.

proposal

The proposals endpoint allows users to query a given proposal. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1
Example Output:
{
  "proposal": {
    "proposal_id": "1",
    "content": null,
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "final_tally_result": {
      "yes": "0",
      "abstain": "0",
      "no": "0",
      "no_with_veto": "0"
    },
    "submit_time": "2022-03-28T11:50:20.819676256Z",
    "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
    "total_deposit": [
      {
        "denom": "stake",
        "amount": "10000000010"
      }
    ],
    "voting_start_time": "2022-03-28T14:25:26.644857113Z",
    "voting_end_time": "2022-03-30T14:25:26.644857113Z"
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1
Example Output:
{
  "proposal": {
    "id": "1",
    "messages": [
      {
        "@type": "/cosmos.bank.v1beta1.MsgSend",
        "from_address": "cosmos1..",
        "to_address": "cosmos1..",
        "amount": [
          {
            "denom": "stake",
            "amount": "10"
          }
        ]
      }
    ],
    "status": "PROPOSAL_STATUS_VOTING_PERIOD",
    "final_tally_result": {
      "yes_count": "0",
      "abstain_count": "0",
      "no_count": "0",
      "no_with_veto_count": "0"
    },
    "submit_time": "2022-03-28T11:50:20.819676256Z",
    "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
    "total_deposit": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ],
    "voting_start_time": "2022-03-28T14:25:26.644857113Z",
    "voting_end_time": "2022-03-30T14:25:26.644857113Z",
    "metadata": "AQ==",
    "title": "Proposal Title",
    "summary": "Proposal Summary"
  }
}

proposals

The proposals endpoint also allows users to query all proposals with optional filters. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals
Example Output:
{
  "proposals": [
    {
      "proposal_id": "1",
      "content": null,
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "final_tally_result": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "no_with_veto": "0"
      },
      "submit_time": "2022-03-28T11:50:20.819676256Z",
      "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ],
      "voting_start_time": "2022-03-28T14:25:26.644857113Z",
      "voting_end_time": "2022-03-30T14:25:26.644857113Z"
    },
    {
      "proposal_id": "2",
      "content": null,
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "final_tally_result": {
        "yes": "0",
        "abstain": "0",
        "no": "0",
        "no_with_veto": "0"
      },
      "submit_time": "2022-03-28T14:02:41.165025015Z",
      "deposit_end_time": "2022-03-30T14:02:41.165025015Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "voting_start_time": "0001-01-01T00:00:00Z",
      "voting_end_time": "0001-01-01T00:00:00Z"
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "2"
  }
}
Using v1:
/cosmos/gov/v1/proposals
Example:
curl localhost:1317/cosmos/gov/v1/proposals
Example Output:
{
  "proposals": [
    {
      "id": "1",
      "messages": [
        {
          "@type": "/cosmos.bank.v1beta1.MsgSend",
          "from_address": "cosmos1..",
          "to_address": "cosmos1..",
          "amount": [
            {
              "denom": "stake",
              "amount": "10"
            }
          ]
        }
      ],
      "status": "PROPOSAL_STATUS_VOTING_PERIOD",
      "final_tally_result": {
        "yes_count": "0",
        "abstain_count": "0",
        "no_count": "0",
        "no_with_veto_count": "0"
      },
      "submit_time": "2022-03-28T11:50:20.819676256Z",
      "deposit_end_time": "2022-03-30T11:50:20.819676256Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10000000010"
        }
      ],
      "voting_start_time": "2022-03-28T14:25:26.644857113Z",
      "voting_end_time": "2022-03-30T14:25:26.644857113Z",
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    },
    {
      "id": "2",
      "messages": [
        {
          "@type": "/cosmos.bank.v1beta1.MsgSend",
          "from_address": "cosmos1..",
          "to_address": "cosmos1..",
          "amount": [
            {
              "denom": "stake",
              "amount": "10"
            }
          ]
        }
      ],
      "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
      "final_tally_result": {
        "yes_count": "0",
        "abstain_count": "0",
        "no_count": "0",
        "no_with_veto_count": "0"
      },
      "submit_time": "2022-03-28T14:02:41.165025015Z",
      "deposit_end_time": "2022-03-30T14:02:41.165025015Z",
      "total_deposit": [
        {
          "denom": "stake",
          "amount": "10"
        }
      ],
      "voting_start_time": null,
      "voting_end_time": null,
      "metadata": "AQ==",
      "title": "Proposal Title",
      "summary": "Proposal Summary"
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "2"
  }
}

voter vote

The votes endpoint allows users to query a vote for a given proposal. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes/cosmos1..
Example Output:
{
  "vote": {
    "proposal_id": "1",
    "voter": "cosmos1..",
    "option": "VOTE_OPTION_YES",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ]
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1/votes/cosmos1..
Example Output:
{
  "vote": {
    "proposal_id": "1",
    "voter": "cosmos1..",
    "options": [
      {
        "option": "VOTE_OPTION_YES",
        "weight": "1.000000000000000000"
      }
    ],
    "metadata": ""
  }
}

votes

The votes endpoint allows users to query all votes for a given proposal. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes
Example Output:
{
  "votes": [
    {
      "proposal_id": "1",
      "voter": "cosmos1..",
      "option": "VOTE_OPTION_YES",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}/votes
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1/votes
Example Output:
{
  "votes": [
    {
      "proposal_id": "1",
      "voter": "cosmos1..",
      "options": [
        {
          "option": "VOTE_OPTION_YES",
          "weight": "1.000000000000000000"
        }
      ],
      "metadata": ""
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}

params

The params endpoint allows users to query all parameters for the gov module. Using legacy v1beta1:
/cosmos/gov/v1beta1/params/{params_type}
Example:
curl localhost:1317/cosmos/gov/v1beta1/params/voting
Example Output:
{
  "voting_params": {
    "voting_period": "172800s"
  },
  "deposit_params": {
    "min_deposit": [
    ],
    "max_deposit_period": "0s"
  },
  "tally_params": {
    "quorum": "0.000000000000000000",
    "threshold": "0.000000000000000000",
    "veto_threshold": "0.000000000000000000"
  }
}
Using v1:
/cosmos/gov/v1/params/{params_type}
Example:
curl localhost:1317/cosmos/gov/v1/params/voting
Example Output:
{
  "voting_params": {
    "voting_period": "172800s"
  },
  "deposit_params": {
    "min_deposit": [
    ],
    "max_deposit_period": "0s"
  },
  "tally_params": {
    "quorum": "0.000000000000000000",
    "threshold": "0.000000000000000000",
    "veto_threshold": "0.000000000000000000"
  }
}

deposits

The deposits endpoint allows users to query a deposit for a given proposal from a given depositor. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits/{depositor}
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits/cosmos1..
Example Output:
{
  "deposit": {
    "proposal_id": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}/deposits/{depositor}
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits/cosmos1..
Example Output:
{
  "deposit": {
    "proposal_id": "1",
    "depositor": "cosmos1..",
    "amount": [
      {
        "denom": "stake",
        "amount": "10000000"
      }
    ]
  }
}

proposal deposits

The deposits endpoint allows users to query all deposits for a given proposal. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits
Example Output:
{
  "deposits": [
    {
      "proposal_id": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}/deposits
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits
Example Output:
{
  "deposits": [
    {
      "proposal_id": "1",
      "depositor": "cosmos1..",
      "amount": [
        {
          "denom": "stake",
          "amount": "10000000"
        }
      ]
    }
  ],
  "pagination": {
    "next_key": null,
    "total": "1"
  }
}

tally

The tally endpoint allows users to query the tally of a given proposal. Using legacy v1beta1:
/cosmos/gov/v1beta1/proposals/{proposal_id}/tally
Example:
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/tally
Example Output:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "no_with_veto": "0"
  }
}
Using v1:
/cosmos/gov/v1/proposals/{proposal_id}/tally
Example:
curl localhost:1317/cosmos/gov/v1/proposals/1/tally
Example Output:
{
  "tally": {
    "yes": "1000000",
    "abstain": "0",
    "no": "0",
    "no_with_veto": "0"
  }
}

Metadata

The gov module has two locations for metadata where users can provide further context about the on-chain actions they are taking. By default all metadata fields have a 255 character length field where metadata can be stored in json format, either on-chain or off-chain depending on the amount of data required. Here we provide a recommendation for the json structure and where the data should be stored. There are two important factors in making these recommendations. First, that the gov and group modules are consistent with one another, note the number of proposals made by all groups may be quite large. Second, that client applications such as block explorers and governance interfaces have confidence in the consistency of metadata structure accross chains.

Proposal

Location: off-chain as json object stored on IPFS (mirrors group proposal)
{
  "title": "",
  "authors": [""],
  "summary": "",
  "details": "",
  "proposal_forum_url": "",
  "vote_option_context": "",
}
The authors field is an array of strings, this is to allow for multiple authors to be listed in the metadata. In v0.46, the authors field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility.

Vote

Location: on-chain as json within 255 character limit (mirrors group vote)
{
  "justification": "",
}

Future Improvements

The current documentation only describes the minimum viable product for the governance module. Future improvements may include:
  • BountyProposals: If accepted, a BountyProposal creates an open bounty. The BountyProposal specifies how many Atoms will be given upon completion. These Atoms will be taken from the reserve pool. After a BountyProposal is accepted by governance, anybody can submit a SoftwareUpgradeProposal with the code to claim the bounty. Note that once a BountyProposal is accepted, the corresponding funds in the reserve pool are locked so that payment can always be honored. In order to link a SoftwareUpgradeProposal to an open bounty, the submitter of the SoftwareUpgradeProposal will use the Proposal.LinkedProposal attribute. If a SoftwareUpgradeProposal linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter.
  • Complex delegation: Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote.
  • Better process for proposal review: There would be two parts to proposal.Deposit, one for anti-spam (same as in MVP) and an other one to reward third party auditors.