Quorum Delegation

Authors: BlockScience and SDF, July 2023

Summary

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.

  • Users choose and rank a set of delegates.
  • If enough ranked users vote, their decision forms a Quorum Vote.
  • The Quorum Vote is cast automatically for the user.
  • QD serves as the first layer in Neural Governance.

This design reduces user attention costs while allowing high flexibility in delegation.

Visualization

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

In this example:

  • The quorum consists of 10 anonymous users.
  • 6 vote "Yes", 2 vote "No", 1 abstains, and 1 does not participate.
  • The quorum participation threshold is 3/5 (6 users).
  • Since "Yes" exceeds simple majority (4 votes), the user automatically votes "Yes".

Use Cases

  • Trust-Set: Delegate to personally trusted individuals.
  • Domain-Specific Bloc: Delegate to topic experts.
  • Balanced Bloc: Delegate across multiple fields (e.g., development, finance, social impact).

User Journey

On the UI:

  1. User chooses to delegate or actively vote.
  2. If delegating, user selects up to MAX_QUORUM_CANDIDATES.
  3. Backend resolves quorum after active votes are tallied:
    • Removes abstainers & delegates.
    • If remaining candidates < QUORUM_SIZE, fills with abstainers.
  4. Backend calculates user vote:
    • Vote is determined by absolute & relative consensus within the quorum.

Module-Specific Adjustments

Parametric Adjustments
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.
Logic Adjustments
  • Allow/disallow re-delegation.
  • Enable public pre-set quorums.
  • Allow users to override quorum votes (e.g., waiting period, notification).
  • Let users set all parameters themselves.
Functional Adjustments
  • Generalizing QD beyond Yes/No/Abstain.
  • Adding pre-selected quorums (e.g., Dev Quorum, Social Quorum).
  • Changing vote weight logic (e.g., using Voting Power instead of 1 vote per candidate).
  • Tracking delegation as a Neuron input (users with more delegations get higher voting power).
  • Making Quorum candidates publicly visible.

Specification

Authors: BlockScience and SDF, July 2023

Introduction

A notebook containing an end-to-end example implementation for this document can be found on the BlockScience/scf-voting-mechanism GitHub repository.

General Definitions

The admissible user actions for the SDF-Voting Mechanism in each round are (mutually exclusive):

  • Vote (yes/no) and Refrain from Voting (abstain) on any number of project submissions
    • A "no" vote will cancel a "yes" vote with the same voting power. In other words, if someone has a Voting Power of 5, then Yes would add +5 to the Project Votes, No would add -5 and Abstain would add 0.
  • Delegate full Voting Power to an ordered list of at least n users - called a Quorum. Quorums are only valid for the current round and should be re-initialized / re-activated for each new round.
    • The Actual Quorum will consist of the first 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.

Logic

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:

  • If all 5 Quorum members vote:
    • 3 Yes, 2 No โ†’ Delegating User automatically votes Yes
    • 2 Yes, 3 No โ†’ Delegating User automatically votes No
  • If 4 Quorum members vote, while one abstains:
    • 3 Yes, 1 No โ†’ Delegating User automatically votes Yes
    • 2 Yes, 2 No โ†’ No absolute majority, Delegating User automatically Abstains.
  • If 3 Quorum Members vote, while two abstain:
    • Quorum Participation Threshold is not met, Delegating User automatically Abstains

Example Implementation in Python

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

Resources

Implementation Instructions

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.

1) Decide on Quorum Delegation Parameters

GitHub Link

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;

2) Implement Base Module

An example for instructions to set this up can be found here.

Tuning Guidelines

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.

Tuning Story 1: When to change the quorum sizes (e.g., max quorum candidates or minimum quorum size)

Candidate Pool:

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.

Quorum Size:

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.

Candidates vs Quorum Size:

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.

Tuning Story 2: When to change the absolute / relative agreement thresholds?

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.

Setting the minimum number of actively voting quorum members:

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.

Setting the absolute agreement threshold:

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.

Tuning Story 3: When to disable/enable re-delegation?

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.

Tuning Story 4: What to do if there are more than yes/no/abstain actions?

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.

Tuning Story 5: Deciding internal Quorum Votes through vote or voting power?

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

Simulations

cadCAD Demo

You can find a demo for cadCAD here: cadCAD Demo

Visualizations

image

image