DAO 2.0: Multiple-Option Proposals and Seamless Aggregate Network Governance
-

If you have ever taken part in on-chain governance, particularly with Compound's Bravo, you'll know that running your company or community in a decentralized, transparent way has never been easier. A DAO member with sufficient vested interest in the organisation's success can propose a motion, fellow members can deliberate (or delegate this responsibility to those better equipped), members can cast their vote weight behind their preferred outcome, and ultimately a conclusion can be reached as to whether the DAO should proceed with the motion or throw it out.
Execution with Nuance
But what if a decision needs to be made that is not a binary-outcome dichotomy? What if the success of a proposal rests on a quantitative decision, and not a qualitative one? Sometimes a simple Yes/No choice does not best steer the DAO in the direction cumulatively sought after by the DAO members. Enter multiple-option (Delta) voting. Built on top of OpenZeppelin's Governor standard as a standalone counting module, Delta proposals can provide a choice of up to 255 outcomes (256 if you count none of the above), predicated upon a few proposer-specified values:
- Number of options: the total number of options in the proposal.
- Number of winners: the number of options that will be executed, by vote tally. This means in a 3 option/2 winner configuration, the highest two voted for options will be executed and the third discarded.
- Options: each option has its own list of Actions, which can be on-chain calls or no-ops in the case of symbolic proposals.
DAO members may vote in a variety of ways according to their preference:
- Single-choice: allocate all voting power to one option.
Approval: split their voting power evenly between multiple options. - Weighted: split their voting power unevenly between multiple options.
- NOTA: if no option seems appealing, none of the above (NOTA).
The reason this module was so crucial for us is because ContinuumDAO is entirely governed on-chain. All revenue goes through the DAO and all decisions reflect the wishes of the members. These kinds of shots cannot be called with a simple For or Against. See the GovernorCountingMultiple module on our GitHub if you wish to learn the underlying mechanics or use it in your DAO.
Under the Hood
Delta proposals are possible as a counting module for Governor by leveraging the
propose(address[] memory targets, uint256[] memory values, bytes[] calldata calldatas, string memory description, address proposer)function signature. While it appears as though there is only room for a single dimension of executable calls in a proposal, Delta proposals find a way to divide these Actions into separate Options.How do we know a proposal is Delta and not Bravo?
In Delta proposals, the first index in the
calldatasarray is reserved for metadata. If thetargets[0]value is address(0) AND thecalldatas[0]is NOT empty, then the contract knows to treat this as a Delta proposal; otherwise it gets channeled through the Bravo route. Ifcalldatas[0]is metadata for a Delta proposal, then it might look like this (ignoring the length prefix):0000000000000000000000000000000000000000000000000000000000000002 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002The metadata is structured as follows:
- First 32 byte slot: number of possible outcomes in the proposal (nOptions)
- Second 32 byte slot: number of winning outcomes in the proposal (nWinner)
- Remaining slots: the index in the overarching targets/values/calldatas array that marks the start of that Option's on-chain Actions.
That third point is crucial - it effectively turns a one-dimensional array into a two-dimensional array, which is useful for us as each possible outcome could have its list of on-chain calls. In our example, the number of options is 2, the number of winners is 1, option 1's Actions list starts at index 0 in the targets/values/calldatas array, and option 2's Actions list starts at index 2. Therefore, we can see that:
- The proposal outlines two possible outcomes
- Only one of them will win, based on which one gets more votes
- Option 1, if successful, would execute two on-chain Actions
- Option 2, if successful, would execute all remaining actions in the array, starting at index 2.
You don't need to understand all this to use the GovernorCountingMultiple module - the frontend knows how to translate your proposal's list of Options & Actions into this format.
Franchising DAO Influence
One of the benefits of developing our cross-chain messaging protocol (Continuum Cross-Chain Caller, or C3Caller) in-house is the option to integrate it in some way into our DAO. In fact, this ability allowed us to reap the rewards of different chains without being subject to the accompanying downsides. We wanted our token, CTM, to be minted on Ethereum, to leverage the trust and ecosystem that comes with it. We also wanted to operate our protocol software (including the DAO!) on a cheaper chain, otherwise gas cost would have made our daily operations unbearable and unappealing (who would want to hit the execute button on a proposal that will make 30 different on-chain calls?). Therefore we didn't compromise. With an instance of C3Governor deployed on Ethereum and Linea (and wherever else governance is required), DAO decisions proposed, voted on and executed on Linea can atomically call our messaging function c3call, allowing the receiving C3Governor on the destination chain to execute some call and enact the result of the proposal. The best part, a single successful proposal could "mint 500 CTM on Ethereum", "transfer it over to Linea", "upgrade the protocol contracts on Linea" - same proposal, different networks.
Under the Hood
C3Governor is built on C3Caller, our cross-chain messaging protocol. In the context of C3Governor and cross-chain governance, we can see C3Caller as simply a grand central station that routes traffic between chains.
A C3Governor instance is deployed on each chain of interest. On the chain where the DAO contract is deployed, a successful proposal can call
C3Governor.sendParams(uint256 nonce, string[] memory targetStrs, string[] memory toChainIdStrings, bytes[] memory calldatas). The nonce is simply the proposal ID.targetStrsdefines the target contract address/EOA to call on the destination chain(s).toChainIdStringsdefines the chain ID corresponding to that index.calldatascontains calldata to execute on the corresponding target.Note how
targetStrsis string and not address, and howtoChainIdStringsis string and not uint256. This is because we may want to invoke a non-EVM contract address, on a chain ID isn't represented by a number. A singlesendParamscall can target multiple destination chain. When called, it broadcasts each call to the C3Caller network.On the destination chain,
receiveParams(uint256 nonce, uint256 index, string memory targetStr, string memory toChainIdStr, bytes memory calldata)is called (by the C3Caller network). Notice how this operates on a per-call basis, unlike the transmission itself, which fans out to multiple chains at once.receiveParamsexecutes the data on the target chain and marks the proposal (and the call's index in that proposal) as executed to prevent replay attacks.This allows for two useful features:
- Contracts on a chain separate to the DAO can set their priviliged address (gov/admin/operator) to the local C3Governor, and trust that only the DAO can invoke it.
- Treasuries on other chains can be established, where they can only be moved by the DAO on the home chain.
Try it out
If you want to include Delta voting in your DAO then check out our GitHub, and stay tuned for our next post on how ContinuumDAO is governed by our Multi-Party Agent wallet.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login