question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Fractional Governance Voting

See original GitHub issue

🧐 Motivation

We’d like to implement a version/extension of Governor that enables delegates to distribute their total voting weight fractionally across For/Against/Abstain. There are many possible uses for this, many of them related to allowing owners of governance tokens which are pooled to participate in Governance votes. Take, for example, cUNI community voting.

📝 Details

In current Governor implementations, a delegate assigns all their voting weight to a single choice: For/Against/Abstain. Specifically, this is implemented concretely in the _countVote implementation in GovernorCountingSimple.

We’d like to implement a version that enables the delegate to split their voting weight fractionally. For example, a delegate with a weight of 1,000 might assign 700 to For, 100 to Against, and 200 to Abstain.

One challenge with this idea is that it can’t trivially be implemented as an extension to the existing Governor implementation or interface in the same way as GovernorCountingSimple.

Specifically, GovernorCountingSimple implements the virtual _countVotes method, which is itself called by the internals of the concrete Governor.

A hypothetical GovernorCountingFractional faces a challenge here, because there is no where in the signature of _countVotes method to include data about how to fractionalize the votes. The only available parameter is the support parameter, but as a uint8, it’s not big enough to hold data about how to split votes between 3 discreet options.

Given this, we’d like some feedback on how we might go about implementing this feature in a way you’d be likely to accept. There are any number of options, but here are a few to consider:

  1. Change the signature of _countVote, and the castVote family of methods, to make support a uint256. With the extra bits, we could pack the vote counts/proportions for each options into the support parameter. This is an easy approach, with minimal surface are for the implementation, but the obvious downside is backwards compatibility as it changes the signatures.
  2. Extend Governor and add a new castWeightedVote family of methods. This works, and contains the fractional voting changes to a single new file, but it feels a bit hacky. The castWeightedVote methods added would exist on the inheriting class, but effectively “redo” much of the functionality from the base Governor. It eschews the benefits of using the inheritance pattern in the first place.
  3. Create a new IGovernorFractional interface and GovernorFractional implementation. In this version, the IGovernorFractional would extend and add fractional voting methods to the IGovernor interface. The GovernorFractional interface would implement the castWeightedVote methods. Finally, a GovernorFractionalCountingSimple implementation would provide a concrete implementation of a new, virtual _countFractionalVote method. Internally, the _countVote implementation could curry to the new _countFractionalVote method for backwards compatibility with the inherited, non-fractional voting methods.

We’re totally open to feedback on this feature proposal, along with the best way to go about implement it. We believe this feature could be beneficial to many projects opting for OZ implementations of Governance. We’re eager and willing to tackle the implementation ourselves and open a PR, but want to do it in a way that will fit with the rest of the library. Thanks in advance for your consideration!

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

2reactions
apbendicommented, Nov 4, 2021

Hey @frangio, thanks so much for the thoughtful response and proposal! I definitely agree in principle that if we could find a way to make this work without changing the interface, that would be ideal. I do see some potential issues with your proposal, though, so let me share some of those to get your thoughts.

First, the precision issue. With 255 steps, the maximum precision we can represent is about 0.4%. This might be an acceptable tradeoff, but it’s not insignificant, especially given one of the top imagined use cases for fractional voting is large pools of governance tokens held in contracts. Take, as a representative example, the cUNI pool, which has something on the order of 10 Million UNI. If that pool were to leverage this method to allow cUNI holders to express their preferences, it could result in an error in weighting of 40,000 UNI. That’s currently >$1 Million in economic weight, and more than enough to swing the result in the case of a modestly contentious vote.

The second issue is related to representing abstentions. If I’m understanding your proposal correctly you’re arguing that splitting “abstain” votes equally between For and Against would have the same effect as Abstain. However, this doesn’t seem to be the case, at least not with regards to the way abstentions are implemented by Bravo (and your concrete implementations which follows Bravo).

In short, in the current system:

  • The quorum threshold is determined by summing For + Abstain
  • The success of a proposal is determined by For > Against

This makes Abstain a discreet option, one which has its own impact on the results of the vote. These impacts are different from splitting the same number of votes between For and Against. Changing the definition of Abstain to mean “split between For and Against” might be a reasonable choice, but it would be a meaningful one that a DAO would have to consider carefully. It certainly would impact the game theoretic ways in which this feature could be used.

One additional complication here: “No Vote”, i.e. not voting at all is, in a way, a discreet option for voting as well. It’s a debatable question whether a fractional voting system should enable a delegate (which, it’s important to remember, might be a contract itself) to vote with only a fraction of their weight, or whether it should require all weight be split between For/Against/Abstain. One could imagine a pool with 1000 weight choosing only to vote with 600 of its voting power 300 For/100 Against/200 Abstain, leaving 400 as “No Vote”. Whether this “should” be implemented or not, it would be nice if the architecture at least made it possible for a project to do so.


With all the above in mind, if you’ll humor me, I want to take a minute to “lobby” for biting the bullet and making the small breaking change to upgrade the support parameter from uint8 to uint256.

Regardless of how we ultimately tackle fractional voting, I think undertaking this exploration has revealed something to me: the current interface is pretty limited in its ability to be extended to implement alternative voting schemes. We can find a way to make fractional voting work, but the underlying limitation may still prove restricting to other experiments in the future.

Let me give a completely contrived example. Imagine some project wanted to enable voting where delegates could express not only their preference For/Against/Abstain, but also an amount. This amount might represent the quantity of governance tokens to be awarded as a grant, and the vote counting implementation might do something like calculate a voting-power-weighted average to determine the final number. So if the proposal was to grant tokens to a community fund, one delegate might vote Against, while another might vote For/10,000, and another might vote For/30,000.

Implementing the above example (which, again, is completely contrived and just meant to be representative of a multitude of possible voting schemes) would be impossible using the current interface. A would-be implementor would have to do something similar to what I’m proposing in Option 2 or Option 3 in the original issue above, except for their specific usecase, instead of for Fractional Voting.

On the other hand, if the support parameter was uint256, they could easily pack both the amount and the preference in the 256 bits available. Their implementation would simply be an extension of Governor with a custom _countVotes method to unpack the bits, and do whatever calculations/storage are necessary.

In other words, the architecture you’ve chosen here— with the virtual _countVotes method that can be overriden and implemented concretely to count votes in any custom way— is a really really nice abstraction, and makes creating custom implementations really clean and easy to reason about. But, it’s currently hamstrung by a lack of flexibility— there’s just not enough surface area to include more data.

Bumping the support param to uint256 is a small change that gives the system significantly more flexibility. Given this has not yet been codified as a standard, or deployed too widely by too many projects, it feels worthwhile to seriously consider making the breaking change.

Ok, that’s my pitch 😅 . Very curious to hear what you think. Thanks again for your time and thoughtful consideration here!

1reaction
frangiocommented, Nov 12, 2021

Yes this is a valid question. My argument in favor of the generic data parameter is that I think we need it supported at the lower level in the core Governor contract, and passed internally to the relevant functions, if we want to build customizations on top of it like the ones we’ve mentioned in this thread.

The external interface can offer functions with specific parameters for fractional voting or other features, but would be implemented by this generic primitive internally.

This can all be solved perhaps with less work by forking the code and making the ad-hoc changes required, but having this generic primitive internally is a building block that can be reused for some of the other use cases that are currently not covered.

The encoding problem is not different from the one already present for the uint8 support parameter. An integrator needs to know how to encode a “For” vote into a uint8 value, for example. The way that we’ve tried to solve this is by having the contract self-document the encoding it expects, through the COUNTING_MODE getter:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e63b09c9ad3a45484b6dc304e0e99640a9dc3036/contracts/governance/extensions/GovernorCountingSimple.sol#L36-L37

Eventually there would be a registry that documents what a particular value for support means. For example, support=bravo means that Against is encoded as 0, For encoded as 1, and Abstain encoded as 2.

If the generic data parameter were directly exposed (as opposed to wrapping the internal mechanism in an ad-hoc external function), what I had in mind was that the contract can similarly document the encoding in COUNTING_MODE, maybe under another key.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Governance - OpenZeppelin Docs
GovernorVotesQuorumFraction : Combines with GovernorVotes to set the quorum as a fraction of the total token supply. Counting modules determine valid voting ......
Read more >
Security - Fractional
Token holders have the ability to continuously vote on a reserve price. The weighted average of this vote decides on the price needed...
Read more >
Moving beyond coin voting governance
Coin voting governance empowers coin holders and coin holder interests at the expense of other parts of the community: protocol communities ...
Read more >
Quadratic Voting in Blockchain Governance - MDPI
Finally, though we consider the possibility of fractional votes, assume that the outcome of a voting round is valid if at least one...
Read more >
What is Fractional.art? - Medium
Fractional is a decentralized protocol that enables collective ownership and governance of one or more NFTs. The tokens function as normal ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found