# Implementation (1/2) ## State ### Procedures and base types `Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive. ```go type DepositProcedure struct { MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period. MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months } ``` ```go type VotingProcedure struct { VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks } ``` ```go type TallyingProcedure struct { Threshold sdk.Dec // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 GovernancePenalty sdk.Dec // Penalty if validator does not vote GracePeriod int64 // If validator entered validator set in this period of blocks before vote ended, governance penalty does not apply } ``` Procedures are stored in a global `GlobalParams` KVStore. Additionally, we introduce some basic types: ```go type Vote byte const ( VoteYes = 0x1 VoteNo = 0x2 VoteNoWithVeto = 0x3 VoteAbstain = 0x4 ) type ProposalType byte const ( ProposalTypePlainText = 0x1 // Plain text proposals ProposalTypeSoftwareUpgrade = 0x2 // Text proposal inducing a software upgrade ) type ProposalStatus byte const ( ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote ProposalStatusAccepted = 0x3 // Proposal has been accepted ProposalStatusRejected = 0x4 // Proposal has been rejected ProposalStatusClosed. = 0x5 // Proposal never reached MinDeposit ) ``` ### Deposit ```go type Deposit struct { Amount sdk.Coins // Amount of coins deposited by depositer Depositer crypto.address // Address of depositer } ``` ### ValidatorGovInfo This type is used in a temp map when tallying ```go type ValidatorGovInfo struct { Minus sdk.Dec Vote Vote } ``` ### Proposals `Proposals` are an item to be voted on. ```go type Proposal struct { Title string // Title of the proposal Description string // Description of the proposal Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit Deposits []Deposit // List of deposits on the proposal SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included Submitter sdk.Address // Address of the submitter VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached CurrentStatus ProposalStatus // Current status of the proposal YesVotes sdk.Dec NoVotes sdk.Dec NoWithVetoVotes sdk.Dec AbstainVotes sdk.Dec } ``` We also mention a method to update the tally for a given proposal: ```go func (proposal Proposal) updateTally(vote byte, amount sdk.Dec) ``` ### Stores *Stores are KVStores in the multistore. 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`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + activeProcedure.VotingPeriod`. If it is, then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. And the pseudocode for the `ProposalProcessingQueue`: ```go in EndBlock do checkProposal() // First call of the recursive function // Recursive function. First call in BeginBlock func checkProposal() proposalID = ProposalProcessingQueue.Peek() if (proposalID == nil) return proposal = load(Governance, ) // proposal is a const key votingProcedure = load(GlobalParams, 'VotingProcedure') if (CurrentBlock == proposal.VotingStartBlock + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) // End of voting period, tally ProposalProcessingQueue.pop() validators = Keeper.getAllValidators() tmpValMap := map(sdk.Address)ValidatorGovInfo // Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished for each validator in validators tmpValMap(validator).Minus = 0 // Tally voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal for each (voterAddress, vote) in voterIterator delegations = stakeKeeper.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 = stakeKeeper.getValidator(voterAddress) if (isVal) tmpValMap(voterAddress).Vote = vote tallyingProcedure = load(GlobalParams, 'TallyingProcedure') // Slash validators that did not vote, or update tally if they voted for each validator in validators if (validator.bondHeight < CurrentBlock - tallyingProcedure.GracePeriod) // only slash if validator entered validator set before grace period if (!tmpValMap(validator).HasVoted) slash validator by tallyingProcedure.GovernancePenalty else 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 > tallyingProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingProcedure.Veto) // proposal was accepted at the end of the voting period // refund deposits (non-voters already punished) proposal.CurrentStatus = ProposalStatusAccepted for each (amount, depositer) in proposal.Deposits depositer.AtomBalance += amount else // proposal was rejected proposal.CurrentStatus = ProposalStatusRejected store(Governance, , proposal) checkProposal() ```