Authors: BlockScience and SDF, July 2023
Quorum Delegation (QD) allows users to passively vote by delegating their choice to a group of users.
Unlike traditional delegation (1:1), QD allows distributed delegation across multiple users.
This design reduces user attention costs while allowing high flexibility in delegation.
In this example:
MAX_QUORUM_CANDIDATES
.< QUORUM_SIZE
, fills with abstainers.Parameter | Description |
---|---|
MAX_QUORUM_CANDIDATES |
Max size of potential candidates a user can select for their Quorum. |
QUORUM_SIZE |
Max number of candidates considered in a Quorum Decision Vote. |
THRESHOLD_QUORUM_PARTICIPATION |
Minimum % of quorum participants needed for a valid vote. |
THRESHOLD_RELATIVE_AGREEMENT |
Minimum % of agreement required among active votes. |
Authors: BlockScience and SDF, July 2023
A notebook containing an end-to-end example implementation for this document can be found on the BlockScience/scf-voting-mechanism GitHub repository.
The admissible user actions for the SDF-Voting Mechanism in each round are (mutually exclusive):
n
users - called a Quorum. Quorums are only valid for the current round and should be re-initialized / re-activated for each new round.
n
users that opted for Voting rather than Delegating. If there are less than n
users any missing slot will be replaced by Abstain votes.Quorum Delegation is a novel delegation scheme inspired by the Stellar Consensus Protocol in which individual users privately select groups of other users that will indirectly determine the user's vote (as long as the group achieves internal consensus).
A user can select up to n
other UUIDs (order sensitive) for creating their Quorum Candidates. The first-ranked n
candidates that opted for Voting during the Round are going to be the Actual Quorum. Users that opted for Delegating are not taken into account for the Actual Quorum (to softly avoid issues around re-delegation).
Depending on the Quorum Consensus, the individual user will automatically vote Yes, No or Abstain for a given project. In order for a Yes or No vote to happen, a Quorum must have an active participation of at least m
(Quorum Participation Threshold) of the members towards that given project (eg. 2/3 of the members did actively vote yes/no rather than abstaining).
If the Quorum Participation Threshold is met, then the Vote Decision for the individual user is going to be the simple majority of the Quorum Members decisions. If there's no majority, then the decision is to abstain.
Some examples can be listed as follows for a Quorum Participation Threshold of 2/3 and Quorum Size of 5:
def query_user_quorum(user_id: UserUUID, max_quorum_size: int=5) -> list[UserUUID]:
"""
Retrieves the Actual Quorum for a given delegating user.
Returns `None` if the User is not delegating.
"""
(action, payload) = USER_ACTIONS[user_id]
actual_quorum = None
if action == Action.Delegate:
actual_quorum = []
current_delegatees = payload
for delegatee in current_delegatees:
delegatee_action = USER_ACTIONS.get(delegatee, None)
if delegatee_action is None:
continue
else:
action = delegatee_action[0]
if action == Action.RoundVote:
actual_quorum.append(delegatee)
if len(actual_quorum) == max_quorum_size:
return actual_quorum
return actual_quorum
else:
return None
def quorum_participation(quorum: list[UserUUID], project_id: ProjectUUID, quorum_size: int=5) -> float:
"""
Active participation share of a quorum towards a project.
"""
active_delegatees = 0
for delegatee in quorum:
if USER_ACTIONS[delegatee][1].get(project_id, None) is not None:
active_delegatees += 1
return active_delegatees / quorum_size
def quorum_agreement(quorum: list[UserUUID], project_id: ProjectUUID, initial_agreement: float=0.0) -> float:
"""
Compute the quorum agreement for the active participants
"""
agreement = initial_agreement
quorum_size = 0
for delegatee in quorum:
delegatee_action = USER_ACTIONS.get(delegatee, None)
action = USER_ACTIONS[delegatee][1].get(project_id, None)
# Note: this logic would be different if we were to weight by User Voting Power
if action is not None:
quorum_size += 1
if action is Vote.Yes:
agreement += 1
elif action is Vote.No:
agreement += -1
else:
agreement += 0
return agreement / quorum_size
def query_user_vote(user_id, project_id) -> Vote | Action:
(action, payload) = USER_ACTIONS.get(user_id, (None, None))
if action is None:
return Vote.Abstain
if action is Action.RoundVote:
project_vote = payload.get(project_id, None)
if project_vote is None:
return Vote.Abstain
else:
return project_vote
if action is Action.Delegate:
return Action.Delegate
def quorum_delegate_result(user_id, project_id, participation_threshold=0.66, agreement_threshold=0.5):
"""
Oracle Module for the Quorum Delegation Neuron.
"""
vote = query_user_vote(user_id, project_id)
if vote is Action.Delegate:
quorum = query_user_quorum(user_id)
if quorum_participation(quorum, project_id) > participation_threshold:
agreement = quorum_agreement(quorum, project_id)
if agreement > agreement_threshold:
return Vote.Yes
elif agreement < -agreement_threshold:
return Vote.No
else:
return Vote.Abstain
else:
return Vote.Abstain
else:
return vote
Authors: BlockScience and SDF, July 2023
A PoC implementation in Rust for Soroban by Alejo Mendoza, Karol Bisztyga, and Mateusz Kowalski is located in the voting-poc GitHub repository. It implements the Neural Governance, Quorum Delegation, and the Trust Graph Bonus governance modules and we'll use it as the basis for providing instructions.
A technical summary for learnings when implementing an MVP version of Neural Quorum Governance on SCF can be found on a post by Karol Bisztyga: SCF Voting Mechanism Implementation.
pub const QUORUM_SIZE: u32 = 5;
pub const QUORUM_PARTICIPATION_TRESHOLD: u32 = 3;
pub const MIN_DELEGATEES: u32 = 5;
pub const MAX_DELEGATEES: u32 = 10;
An example for instructions to set this up can be found here.
Authors: BlockScience and SDF, July 2023
Quorum Delegation can be tuned to capture phenomena within a community. Besides parametrizing, additional logical adjustments can be made.
The quorum candidates are limited by the maximum amount of other users that might be taken as input for a quorum. Setting this value lower requires users to put more thought into their choice, but might result in insufficient active voters to successfully come to a quorum agreement. Setting this value higher allows users to make a wider selection of candidates, but could result in unintended outcomes for the user if their primary candidates do not actively vote, leaving only back-up candidates for the quorum.
Allowing lower quorum sizes can increase flexibility for users, such as allowing for traditionally known delegation with only 1 other user being delegated to. However, low quorum sizes can increase the likelihood of users accidentally abstaining due to their delegates not voting. Setting a higher minimum requires users to choose more candidates, which decreases the effect of individual delegates. At the same time, this increases the cost of time for users to delegate.
Setting the maximum amount of candidates equal to the quorum size increases the risk of a user accidentally abstaining, as no further candidates can be drawn from to form a successful quorum. Increasing the gap between candidates and quorum size allows for a higher likelihood of a successful quorum, while also increasing the risk of less representative voting outcomes if backup candidates do not reflect a user's opinion as accurately.
A quorum comes to an agreement (which results in a user voting with their choice) only when a certain number of quorum members actively vote, and when these quorum members reach a set agreement threshold.
Lowering the fraction of required active votes increases the likelihood of a successful delegation vote, while also increasing the potential effects of individual delegates. Raising the fraction of required active votes decreases the likelihood of a successful delegation vote, while also decreasing the potential effects of individual delegates.
A community might face a decision where the expected impact is highly specialized. Increasing the absolute majority threshold can result in quorum votes to only be counted when quorums are in high agreement, while a larger band is allocated for non-voting due to missing agreement.
Re-delegation of votes โ as in User A delegates to User B, while User B delegates to User C โ can enable more complex voting scenarios to be represented. As an example, a community might face a decision that only a smaller subset is truly knowledgeable about, while this subset is not widely known. Allowing for redelegation could channel voting decisions through this community, until it reaches those who are more certain about their vote. However, this increases the risk of many users abstaining, as circular delegation can result in quorums not reaching their agreement thresholds.
Currently, Quorum Delegation represents three choices โ yes, no, abstain. However, certain decisions require more nuanced representation and might require voters to pick a parameter value that they see as most representative of their opinion. In such a case, Quorum Voting could help to aggregate values picked by a representative sample chosen by the user. In this scenario, QD would need to be tuned to accept values, aggregate them, then provide as output a direct vote by the user on the aggregated value.
In the reference PoC implementation, a quorum's outcome is decided by individual votes of delegates. This puts equal importance on each quorum member. Alternatively, one might decide that the voting power of each member should factor into the internal vote decision, or that users could set individual weights for quorum candidates (which can be somewhat similarly enabled through allowing users to set the same candidate more than once for their quorum).
You can find a demo for cadCAD here: cadCAD Demo