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.
Vote: Participants can vote on proposals that reached MinDeposit
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, the deposit is burned.
This module will be used in the Cosmos Hub, the first Hub in the Cosmos network.
Features that may be added in the future are described in Future Improvements.
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.
Disclaimer: This is work in progress. Mechanisms are susceptible to change.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.
Every account can submit proposals by sending a MsgSubmitProposal transaction.
Once a proposal is submitted, it is identified by its unique proposalID.
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.
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).
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.
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.
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. Voting period should always be shorter than
Unbonding period to prevent double voting. The initial value of
Voting period is 2 weeks.
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.
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.
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 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%.
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.
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.
Constitution is found in the genesis state. It is a string field intended to be used to descibe 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 expecations placed on them while operating thier 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 whishing 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 constitutionAs 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 constutituon can provide guidance to the community.
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
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:
Copy
Ask AI
{ "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.
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 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.
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
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:
Copy
Ask AI
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)
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 proposal are defined by their types.While proposals should use the new implementation of the governance proposal, we need still to use legacy proposal in order to submit a software-upgrade and a cancel-software-upgrade proposal.More information on how to submit proposals in the client section.
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.State modifications:
Generate new proposalID
Create new Proposal
Initialise 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
A MsgSubmitProposal transaction can be handled according to the following
pseudocode.
Copy
Ask AI
// PSEUDOCODE //// Check if MsgSubmitProposal is valid. If it is, create proposal //upon receiving txGovSubmitProposal from sender do if !correctlyFormatted(txGovSubmitProposal) // check if proposal is correctly formatted and the messages have routes to other modules. Includes fee payment. // check if all messages' unique Signer is the gov acct. // check if the metadata is not too long. throw initialDeposit = txGovSubmitProposal.InitialDeposit if (initialDeposit.Atoms <= 0)OR (sender.AtomBalance < initialDeposit.Atoms) // InitialDeposit is negative or null OR sender has insufficient funds throw if (txGovSubmitProposal.Type != ProposalTypePlainText)OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade)sender.AtomBalance -= initialDeposit.Atoms depositParam = load(GlobalParams, 'DepositParam')proposalID = generate new proposalID proposal = NewProposal()proposal.Messages = txGovSubmitProposal.Messages proposal.Metadata = txGovSubmitProposal.Metadata proposal.TotalDeposit = initialDeposit proposal.SubmitTime = <CurrentTime> proposal.DepositEndTime = <CurrentTime>.Add(depositParam.MaxDepositPeriod)proposal.Deposits.append({ initialDeposit, sender})proposal.Submitter = sender proposal.YesVotes = 0 proposal.NoVotes = 0 proposal.NoWithVetoVotes = 0 proposal.AbstainVotes = 0 proposal.CurrentStatus = ProposalStatusOpen store(Proposals, <proposalID|'proposal'>, proposal) // Store proposal in Proposals mapping return proposalID
Once a proposal is submitted, if
Proposal.TotalDeposit < ActiveParam.MinDeposit, Atom holders can send
MsgDeposit transactions to increase the proposal’s deposit.
Increase proposal.TotalDeposit by sender’s deposit
If MinDeposit is reached:
Push proposalID in ProposalProcessingQueueEnd
Transfer Deposit from the proposer to the governance ModuleAccount
A MsgDeposit transaction has to go through a number of checks to be valid.
These checks are outlined in the following pseudocode.
Copy
Ask AI
// PSEUDOCODE //// Check if MsgDeposit is valid. If it is, increase deposit and check if MinDeposit is reachedupon receiving txGovDeposit from sender do // check if proposal is correctly formatted. Includes fee payment. if !correctlyFormatted(txGovDeposit)throw proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) // proposal is a const key, proposalID is variable if (proposal == nil) // There is no proposal for this proposalID throw if (txGovDeposit.Deposit.Atoms <= 0)OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms)OR (proposal.CurrentStatus != ProposalStatusOpen) // deposit is negative or null // OR sender has insufficient funds // OR proposal is not open for deposit anymore throw depositParam = load(GlobalParams, 'DepositParam') if (CurrentBlock >= proposal.SubmitBlock + depositParam.MaxDepositPeriod)proposal.CurrentStatus = ProposalStatusClosed else // sender can deposit sender.AtomBalance -= txGovDeposit.Deposit.Atoms proposal.Deposits.append({ txGovVote.Deposit, sender})proposal.TotalDeposit.Plus(txGovDeposit.Deposit) if (proposal.TotalDeposit >= depositParam.MinDeposit) // MinDeposit is reached, vote opens proposal.VotingStartBlock = CurrentBlock proposal.CurrentStatus = ProposalStatusActive ProposalProcessingQueue.push(txGovDeposit.ProposalID)store(Proposals, <txGovVote.ProposalID|'proposal'>, proposal)
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.
Gas cost for this message has to take into account the future tallying of the vote in EndBlocker.
Next is a pseudocode outline of the way MsgVote transactions are handled:
Copy
Ask AI
// PSEUDOCODE // // Check if MsgVote is valid. If it is, count vote// upon receiving txGovVote from sender do // check if proposal is correctly formatted. Includes fee payment. if !correctlyFormatted(txGovDeposit)throw proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) if (proposal == nil) // There is no proposal for this proposalID throw if (proposal.CurrentStatus == ProposalStatusActive) // Sender can vote if // Proposal is active // Sender has some bonds store(Governance, <txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
The governance module contains the following parameters:
Key
Type
Example
min_deposit
array (coins)
[{"denom":"uatom","amount":"10000000"}]
max_deposit_period
string (time ns)
“172800000000000” (17280s)
voting_period
string (time ns)
“172800000000000” (17280s)
quorum
string (dec)
“0.334000000000000000”
threshold
string (dec)
“0.500000000000000000”
veto
string (dec)
“0.334000000000000000”
expedited_threshold
string (time ns)
“0.667000000000000000”
expedited_voting_period
string (time ns)
“86400000000000” (8600s)
expedited_min_deposit
array (coins)
[{"denom":"uatom","amount":"50000000"}]
burn_proposal_deposit_prevote
bool
false
burn_vote_quorum
bool
false
burn_vote_veto
bool
true
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.
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.
Copy
Ask AI
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.
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.
Copy
Ask AI
simd tx gov cancel-proposal [proposal-id] [flags]
Example:
Copy
Ask AI
simd tx gov cancel-proposal 1 --from cosmos1...
vote
The vote command allows users to submit a vote for a given governance proposal.
Copy
Ask AI
simd tx gov vote [command] [flags]
Example:
Copy
Ask AI
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.
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.
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.
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.