openlawteam / tribute-contracts

A new modular DAO framework, inspired by the Moloch smart contracts

Home Page:https://tributedao.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

cost effective voting with merkle trees

adridadou opened this issue · comments

Why

Onchain governance is always expensive because it involves so many transactions. The goal of this document is to discuss an approach to bring most of the voting mechanisms off chain but still have the same level of verifiability with a few assumptions. The goal here is to describe those assumptions, see if they are reasonable or not and maybe get feedback on how to improve this approach even further.

The properties we are looking for are:
near constant gas cost for a vote round
reasonable cost to create a vote
simple approach
on chain verification / audit possible if something doesn’t seem right

Design

The goal here is to create two merkle tree:
The vote definition merkle tree. It represents every voting rights
The vote results merkle tree. It represents the result of the vote

The vote definition root is being submitted at the same time as we create the vote onchain.
The vote results are being submitted once the voting has ended.

VoteRoundId

Each round needs to be uniquely identified. To do this, we use the function: hash(Moloch address, proposalId) which is always unique.

Voting

A Voter should be able to vote as well. To do this, the user needs to signs with his key the following data:
for yes: hash(voteRoundId, 1)
for No: hash(voteRoundId, -1)
for abstain : hash(voteRoundId, 0)

The vote definition merkle tree

Its structure is as follows:
leaf: voter address, voter weight. The hash representation of the leaf (used for parent nodes) is hash ( voter address, voter weight)
node: standard merkle tree node, i.e. hash of its children

Each leaf is sorted by the address

The vote results merkle tree

Its structure is as follows:

leaf

it has the entries:

  • voteSignature. // the actual vote
  • balance (1, 0 , -1 )
    The hash being used by the parent node is hash(voteSignature, balance)

node

It has the same structure but the function is a bit different

  • hashOfChildren
  • accumulation (sum of accumulation of its children)

root

then the hash of the root is being published with the accumulation result. The accumulation result is the result of the voting

Happy path

If everyone is happy with the voting result (no fraud has been committed) then the DAO just take the result and acts accordingly.

Fraud

This is the most important part. We describe here the fraud / attack vector, and how this system can respond to them.

Wrong data

If someone tries to vote with the wrong data (not signed by the right key, not the right weight etc ...) anyone can publish the proof that the leaf was wrong by publishing the incriminating leaf with its path to root, the proof in the vote definition and show that the data doesn't match.

unknown address

if someone shouldn't be able to vote, anyone can publish a proof that the address doesn't exist in the vote definition tree.
The way to prove that is as follows:

  • get an address that should be before and after the missing address
  • get all the parents to come with it and prove it's part of the tree

if. the address is smaller or higher than any other address, just prove that the smaller / highest address in the tree definition is not this address and is higher / smaller than the one that has voted

Failsafe

if anything goes wrong, we should make it possible to invalidate an offchain voting and switch back to onchain if a quorum of voters mark it as fraudulent. TBD: do we want to just cancel the vote or switch it to on chain ? maybe configurable ?

Staking

It seems to me that using some form of staking to reward the participants and also having a way to punish bad behaviour makes sense but the details of this part is still TBD

Assumptions

In order for this to work, we make a few assumptions:

  • The vote definition tree is a sorted Merkle tree and we assume it is the case ( it is possible to add a way to prove fraud here (by showing two nodes that are not in order)
  • The vote definition tree is available for anyone to check
  • Once the vote happened, the vote result merkle tree is also available to anyone to check

Smart Contract design

TBD: Let's start with the high level design and then I can work on the smart contract itself

I think for FAILSAFE situations - If there was enough incentive to commit a fraudulent vote (a certain gov proposal would unfairly benefit a certain member), and if a quorom of voters think something wrong happened, then we should move to on-chain. A FAILSAFE scenario means that the stakes were high enough to warrant on-chain voting.

So for me the whole conflict resolution is still TBD
My biggest fear here is:

  • there is a clear delay attack here. We should make sure that anyone who tries to delay the process should put something at stake. Otherwise it becomes a clear attack vector
  • We need to define what the Failsafe means and how to handle it.
  • See how much we can recover without going to the naive onchain voting (for example how easy it is to update the merkle tree onchain)

from our slack conversation..
If we are using Snapshot, how do we account for any rage quitting in between block times?
....
If a member voted No then they can ragequit, otherwise if they voted YES, then they have to wait until the last proposal voted YES on is processed. From the moloch code a member-- "cannot ragequit until highest index proposal member voted YES on is processed"

Modified idea to manage voting offchain

Snapshot:
Ordered merkle tree with all the participants and their vote weight

Result:
the merkle tree is built as follows:
The tree itself is a standard merkle tree
The leaves are built like this:
they have the following properties:

  • address (address)
  • weight (uint256)
  • vote (bytes)
  • nbYes (uint256)
  • nbNo (uint256)
  • previous vote address (address)
  • next vote address (address)

address : the address of the person who votes
weight: how much his vote weights
vote: the signed version of the vote
nbYes: accumulator, how many votes have been yes
nbNo: accumulator, how many votes have been no
previous vote address: address of the previous vote (0x0 if none). makes it easier to check sequential steps
next vote address: address of the next vote (0x0 if none). makes it easier to check sequential steps and to see that one vote is the last one

each step should be order by address

Challenging a result:

prove that someone's vote is not correct (wrong weight, wrong signature or wrong type (yes instead of no for ex))

  • give the path from root to the voter details in the snapshot
  • show the path from the root to the particular vote
  • pass the values of the leaf (address ,weight, vote, nbYes, nbNo)
  • the smart contract verifies data integrity

prove that a step is wrong

  • give the path from root to the voter details in the result where the calculation was wrong
  • give the path from root to the previous vote in the result
  • the smart contract checks the step from n - 1 to n and if the calculation

prove that the steps are out of order
-give proofs of two adjacent steps

  • show that they are out of order

Accepting a vote:

  • If more than 50% of potential voters have voted yes or no
  • If the voting period is finished

If the quorum has been reached, no need to wait anymore and we close the voting. Censorship doesn't matter in this case because any additional vote wouldn't change the result.
The only thing we need to make sure is that the result is correct. A grace period (configurable, by default 1 day) to wait for anyone to challenge the result

If the voting period is finished and an absolute majority has not been reached, then it is a bit trickier:
A vote result can be published.
Let's say the % of voters is n.

Then once the result has been published, a grace period starts (configurable, default 1 day)
If someone that should be able to vote prove that his vote has not been taken into account, then the result is cancelled and a new result with at least n + voting weight of the person missing needs to be submitted.

If the new result is > 50 %, then apply the rule above, otherwise, go bac to the rule of grace period and possibility to say that you have not been taken into account.

Security:

  • give incentive for people to vote on proposals. getting more than 50% ensure speedy process
  • fallback for low participation, may be subject to delay attack but can be mitigated by getting more people to vote
  • easy challenge and onchain validation and virtually impossible to temper with

Assumption:

  • Data availability is assumed here

staking / reward strategy
This part is open for debate / discussion. I think we need a bit of both
Anyone starting a vote or proposing a result should have tokens at stake
If anyone proves that the result is bad, then the tokens at stake should come back to him
if someone says he hasn't been taken into account in the vote and someone else proposes a result, the new person should get the reward from the tokens at stake

from Slack... we're trying to solve 1) spam 2) getting sufficient participation, 3) and dealing with bad result. -- A possible solution is a Swiss style of voting, where a member has to collect a signature of another member(s) and pool together enough signatures to bring to call for a vote. This could solve 1 and 2, because if you have to collect signatures then the issue is relevant/material enough to vote on.