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.
Copy
// TallyParams defines the params for tallying votes on governance proposals.
message TallyParams {
// Minimum percentage of total stake needed to vote for a result to be
// considered valid.
bytes quorum = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "quorum,omitempty"
];
// Minimum proportion of Yes votes for proposal to pass. Default value: 0.5.
bytes threshold = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "threshold,omitempty"
];
// Minimum value of Veto votes to Total votes ratio for proposal to be
// vetoed. Default value: 1/3.
bytes veto_threshold = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "veto_threshold,omitempty",
(gogoproto.moretags) = "yaml:\"veto_threshold\""
];
}
Parameters are stored in a global GlobalParams KVStore.
Additionally, we introduce some basic types:
Copy
type Vote byteconst(
VoteYes =0x1
VoteNo =0x2
VoteNoWithVeto =0x3
VoteAbstain =0x4)type ProposalType stringconst(
ProposalTypePlainText ="Text"
ProposalTypeSoftwareUpgrade ="SoftwareUpgrade")type ProposalStatus byteconst(
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)
Proposal objects are used to account votes and generally track the proposal's state. They contain Content which denotes
what this proposal is about, and other fields, which are the mutable state of
the governance process.
Copy
type Content interface{GetTitle()stringGetDescription()stringProposalRoute()stringProposalType()stringValidateBasic() sdk.Error
String()string}
The Content on a proposal is an interface which contains the information about
the Proposal such as the tile, description, and any notable changes. Also, this
Content type can by implemented by any module. The Content's ProposalRoute
returns a string which must be used to route the Content's Handler in the
governance keeper. This allows the governance keeper to execute proposal logic
implemented by any module. If a proposal passes, the handler is executed. Only
if the handler is successful does the state get persisted and the proposal finally
passes. Otherwise, the proposal is rejected.
Copy
type Handler func(ctx sdk.Context, content Content) sdk.Error
The Handler is responsible for actually executing the proposal and processing
any state changes specified by the proposal. It is executed only if a proposal
passes during EndBlock.
We also mention a method to update the tally for a given proposal:
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 two 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.
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
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 votesfor each validator in validators
tmpValMap(validator.OperatorAddr).Minus =0// Tally
voterIterator =rangeQuery(Governance,<proposalID|'addresses'>)//return all the addresses that voted on the proposalfor each (voterAddress, vote) in voterIterator
delegations = stakingKeeper.getDelegations(voterAddress)// get all delegations for current voterfor each delegation in delegations
// make sure delegation.Shares does NOT include shares being unbondedtmpValMap(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 they votedfor each validator in validators
iftmpValMap(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)