On-chain governance
Governance Address
All on-chain governance mechanisms are handled under a single address, referred to as the GovernanceAddress
.
The GovernanceAddress
is created during genesis, and handles the verification of submitted proposals, the tallying of votes, and the execution of proposals.
This address also stores all previous proposals under its address space.
Proposals are submitted through the client, and are verified by the GovernanceAddress
before being added to the pending proposals list.
The structure of proposals is outlined here.
The correct logic to handle these different types is hardcoded in protocol. We'll also rely on type checking to strictly enforce the correctness of a proposal given its type. These two approaches combined will prevent a user from deviating from the intended logic for a certain proposal type (e.g. providing a wasm code when it's not needed or allowing only validators to vote when also delegators should, etc...). More details on the specific types supported can be found in the relative section of the proposals page.
GovernanceAddress VP
The GovernanceAddress
validity predicate (VP) task is to check the integrity and correctness of new proposals.
Submission validation
A proposal must satisfy the following mandatory storage writes:
counter
- The number of proposals submitted so farauthor
- The address of the author of the proposaltype
- The proposal typefunds
- The amount of funds locked for this proposalvoting_start
- The epoch specifying when the voting period startsvoting_end
- The epoch specifying when the voting period endsgrace_epoch
- The epoch specifying when the proposal becomes active, (and attached WASM code is executed if any), given that the proposal has a positive outcome.
Further, it must check that the proposal satisfies the following constraints:
- The attached
funds
is >=min_proposal_fund
, a protocol parameter - The
id
of the proposal is unique - The attached
ProposalType
is supported by the protocol - The difference between StartEpoch and EndEpoch is >=
min_proposal_period
- There is an attached
description
of the proposal with character length <max_proposal_content_size
- The difference between the
voting_end
andvoting_start
epoch must be divisible by 3, i.e(EndEpoch - StartEpoch) % 3 == 0
. - The difference between
grace_epoch
andvoting_end
is of at leastmin_proposal_grace_epochs
, a protocol parameter. The reason for this constraint is explained below.
Voting validation
Once a proposal has been accepted by the protocol as valid, it will be added to the pending proposals list, and delegators and delegates will be able to vote on it. The VP must also check that voting adheres to the following constraints:
- The voter is a delegator or a delegate (further constraints can be applied depending on the proposal type)
- Given that non-validating accounts can vote, validators may only vote during the initial of the whole proposal duration (
voting_end
-voting_start
)
Once a proposal has been created, nobody can modify any of its fields.
Execution of WASM code
The VP is also responsible for handling the execution of WASM code attached to a DefaultProposal
proposal type.
Examples of such code execution could be:
- storage writes to change some protocol parameter
- storage writes to restore a slash
This means that corresponding VPs will also be invoked.
Proposal transaction
The on-chain proposal transaction will have the following structure, where author
address will be address of the account submitting the proposal, and hence the address which receives any deposited funds.
struct Proposal {
id: u64,
content: Vec<u8>,
author: Address,
r#type: ProposalType, // This is an enum, in which Wasm code is embedded for DefaultProposal
votingStartEpoch: Epoch,
votingEndEpoch: Epoch,
graceEpoch: Epoch,
}
The optional proposal wasm code will be embedded inside the ProposalType
enum variants to better perform validation through type checking.
Vote transaction
Vote transactions have the following structure:
struct OnChainVote {
id: u64,
voter: Address,
yay: ProposalVote,
}
where ProposalVote
is an enum representing a Yay
or Nay
vote: the yay variant also contains the specific memo (if any) required for that proposal.
Storage writes for a vote transaction are described here.
If non-validating accounts are allowed to vote, delegates will be able to vote only for of the total voting period, while delegators can vote until the end of the voting period.
If only validators are allowed to vote for the ProposalType
in question, they are allowed to vote for the entire voting window.
If a delegator votes differently to its delegate, the delegate's vote will be overriden (e.g. if a delegator has a voting power of 200 and votes opposite to the delegate, then 200 will be subtracted from the voting power of the involved delegate).
Tallying votes
Proposals are tallied at the start of their grace_epoch
during the finalize_block
function.
The tallying is based off of the votes collected at the end of the voting_end
epoch.
If the threshold specified by the ProposalType is reached, the proposal will be considered successful.
There are two types of thresholds:
- Fraction of total staked
NAM
that voted - This checks whether enough stakedNAM
voted for the proposal at all. - Fraction of votes in favor of the proposal - This checks whether enough votes (weighted by their staked
NAM
) voted in favor of the proposal, out of the stakedNAM
that did vote.
E.g if the thresholds, respectively, are and , then at least 50% of the total staked NAM
must have voted for the proposal AND out of this NAM, to be accepted.
Tallying is computed with the following rules:
- Sum all the voting power of delegates that voted
Yay
, call this sumSumYay
- Sum all the voting power of delegates that voted
Nay
, call this sumSumNay
- Check if the sum
SumYay
+SumNay
meets the first threshold, if not, the proposal outcome is negative - For any delegate that voted
Yay
, subtract the voting power of any delegator that votedNay
fromSumYay
- For any delegate that voted
Nay
, subtract the voting power of any delegator that votedYay
fromSumNay
- Add voting power for any delegation that voted
Yay
(whose corresponding delegate didn't voteYay
) toSumYay
- Add voting power for any delegation that voted
Nay
(whose corresponding delegate didn't voteNay
) toSumNay
- If the sum
SumYay
is greater than or equal to the sumSumNay
, ANDSumYay
divided by the totalbonded-stake
is greater or equal to the threshold set byProposalType
, the proposal outcome is positive, otherwise negative
All the computation is done at the start of grace_epoch
on data collected at the epoch specified in the voting-end
field of the proposal.
Refund and Proposal Execution mechanism
In parallel to tallying, the protocol manages the execution of accepted proposals and refunding in the finalize_block
function.
If the proposals grace_epoch
matches with the current epoch, AND the proposal had a positive outcome, the protocol refunds the locked funds from GovernanceAddress
to the proposal author address (specified in the proposal author
field).
Moreover, if the proposal had a positive outcome and had attached WASM code, the code is executed immediately.
On the other hand, should the proposal be rejected (negative outcome), any locked funds is burnt (removed from total supply).
The result is then signaled by creating and inserting a CometBFT Event
(opens in a new tab).