209 lines
8.0 KiB
Markdown
209 lines
8.0 KiB
Markdown
<!--
|
|
order: 2
|
|
-->
|
|
|
|
# State
|
|
|
|
## Proposals
|
|
|
|
`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
|
|
|
|
+++ https://github.com/cosmos/cosmos-sdk/blob/4a129832eb16f37a89e97652a669f0cdc9196ca9/proto/cosmos/gov/v1beta2/gov.proto#L42-L52
|
|
|
|
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, an array of bytes, 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 contain a URL or some form of CID using a system such as
|
|
[IPFS](https://docs.ipfs.io/concepts/content-addressing/). To support the case of
|
|
interoperability across networks, the SDK recommends that the `metadata` represents
|
|
the following `JSON` template:
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
### Writing a module that uses governance
|
|
|
|
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 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
|
|
|
|
+++ 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 introduce some basic types:
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
type ValidatorGovInfo struct {
|
|
Minus sdk.Dec
|
|
Vote Vote
|
|
}
|
|
```
|
|
|
|
## 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`:
|
|
|
|
```go
|
|
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)
|
|
```
|