# 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 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

Copy // DepositParams defines the params for deposits on governance proposals. message DepositParams { // Minimum deposit for a proposal to enter voting period. repeated cosmos.base.v1beta1.Coin min_deposit = 1 [ (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.moretags) = "yaml:\"min_deposit\"", (gogoproto.jsontag) = "min_deposit,omitempty" ]; // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 // months. google.protobuf.Duration max_deposit_period = 2 [ (gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.jsontag) = "max_deposit_period,omitempty", (gogoproto.moretags) = "yaml:\"max_deposit_period\"" ]; }

# VotingParams

Copy // VotingParams defines the params for voting on governance proposals. message VotingParams { // Length of the voting period. google.protobuf.Duration voting_period = 1 [ (gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.jsontag) = "voting_period,omitempty", (gogoproto.moretags) = "yaml:\"voting_period\"" ]; }

# TallyParams

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 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

Copy // Deposit defines an amount deposited by an account address to an active // proposal. message Deposit { option (gogoproto.goproto_getters) = false; option (gogoproto.equal) = false; uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; string depositor = 2; repeated cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; }

# ValidatorGovInfo

This type is used in a temp map when tallying

Copy 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 this proposal is about, and other fields, which are the mutable state of the governance process.

Copy // Proposal defines the core field members of a governance proposal. message Proposal { option (gogoproto.equal) = true; uint64 proposal_id = 1 [(gogoproto.jsontag) = "id", (gogoproto.moretags) = "yaml:\"id\""]; google.protobuf.Any content = 2 [(cosmos_proto.accepts_interface) = "Content"]; ProposalStatus status = 3 [(gogoproto.moretags) = "yaml:\"proposal_status\""]; TallyResult final_tally_result = 4 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"final_tally_result\""]; google.protobuf.Timestamp submit_time = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"submit_time\""]; google.protobuf.Timestamp deposit_end_time = 6 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"deposit_end_time\""]; repeated cosmos.base.v1beta1.Coin total_deposit = 7 [ (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.moretags) = "yaml:\"total_deposit\"" ]; google.protobuf.Timestamp voting_start_time = 8 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"voting_start_time\""]; google.protobuf.Timestamp voting_end_time = 9 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"voting_end_time\""]; }

Copy type Content interface { GetTitle() string GetDescription() string ProposalRoute() string ProposalType() string ValidateBasic() 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:

Copy 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`

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

# 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:

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 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)