State
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 the community wants to change
a parameter set through governance, either by modifying a value or adding/removing
a parameter field, a new parameter set has to be created, and the previous one has
to be rendered inactive.
DepositParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L127-L145
VotingParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L147-L156
TallyParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L158-L183
Parameters are stored in a global GlobalParams
KVStore.
Additionally, we introduced 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
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L43-L53
ValidatorGovInfo
This type is used in a temp map when tallying:
type ValidatorGovInfo struct {
Minus sdk.Dec
Vote Vote
}
Proposals
Proposal
objects are used to account votes and generally track the proposal's state. They contain Content
which denotes
what the proposal is about, and other fields that compose the mutable state of
the governance process.
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/gov/v1beta1/gov.proto#L55-L77
type Content interface {
GetTitle() string
GetDescription() string
ProposalRoute() string
ProposalType() string
ValidateBasic() sdk.Error
String() string
}
The Content
on a proposal is an interface containing information about
the Proposal
such as the title, description, and any notable changes. Also, this
Content
type can be implemented by any module. The Content
's ProposalRoute
returns a string which must be used in order 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 voting stage, the handler is executed. Only
if the handler is successful does the state get persisted, and the proposal finally marked as passed.
Otherwise, the proposal is rejected.
type Handler func(ctx sdk.Context, content Content) sdk.Error
The Handler
is responsible for the execution of the proposal, and the processing
of any state changes specified by the proposal. It is only executed if a proposal
successfully passes during EndBlock
.
A method can be called to update the tally for a given proposal:
func (proposal Proposal) updateTally(vote byte, amount sdk.Dec)
Stores
Stores are KVStores in the multi-store. The key to find the store is the first parameter in the list.
One KVStore Governance
is used to store two mappings:
- A mapping from
proposalID|'proposal'
toProposal
. - A mapping from
proposalID|'addresses'|address
toVote
. This mapping allows us to query all addresses that voted on the proposal along with their votes, by doing a range query onproposalID:addresses
.
For pseudocode purposes, two functions used to read and write in stores are:
load(StoreKey, Key)
: Retrieve item stored at keyKey
in store found at keyStoreKey
in the multistorestore(StoreKey, Key, value)
: Write valueValue
at keyKey
in store found at keyStoreKey
in the multistore
Proposal Processing Queue
Store:
ProposalProcessingQueue
: A queuequeue[proposalID]
containing all theProposalIDs
of proposals that reachedMinDeposit
. During eachEndBlock
, 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, the deposits are refunded. Finally, the proposal contentHandler
is executed.
Below is 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 they 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)