Disclaimer: no mathematicians were harmed in the writing of this article. That said, some mathematicians might experience varying degrees of pain when faced with my mathematical mistakes. Reader discretion is advised.
Let's say that you want to let people in your community vote for initiatives that matter to them. You want each person to be able to cast a vote on multiple initiatives, and to give a different weight to each vote. The vote has a deadline, meaning a date and time at which the votes will be calculated and the initiatives picked. Voting isn't mandatory, you want to avoid setting a quorum. What are the options?
The context here is more complicated than that, because we are talking about voting on the blockchain where votes are mainly expressed with ERC-20 tokens. This tends to skew the balance towards the preference of token-rich voters, since they can afford to put more on the initiatives they care about.
Another issue that needs to be addressed is the ability of malicious actors to buy votes. This is a problem since ERC-20 tokens are by their own nature tradable.
What are your options?
You could use quadratic voting. The way quadratic voting works is simple: each vote you cast has a cost, equal to the number of votes squared.
Here's what that looks like:
|Number of Votes||Cost|
To put it in other words, the vote you cast is equal to the square root of the amount you pay. You can see that the price goes up pretty quickly, making it harder for a single person to influence the result.
There's a problem though: what if someone creates multiple wallets and votes multiple times on the same proposal with small amounts? It is a simple and low effort workaround that will render this mechanic useless.
You need a better strategy.
Here's where conviction voting comes in. The way conviction voting works is conceptually simple:
- Users cast their vote by locking their tokens
- The longer the token is locked the more weight the vote has
This makes buying votes much more expensive: in a way, we can say that a malicious actor is now renting votes.
Normally for conviction voting you would use a formula like this:
Where is the initial cost of the vote, and is the score at the time the vote was withdrawn.
Which is equivalent to:
Since is a decay factor, meaning that , the first term is an exponential decay function. The second term is a function that converges asymptotically to :
Given all the above, what does the conviction curve look like? Assuming , we get that . With e.g. and , our conviction curve will look like this:
If we instead assume (meaning the vote was withdrawn), we get , which by itself looks like this (with and ):
We can now combine the two, which gives us this:
That looks much better, and the decay can help prevent last minute swings due to malicious actors intentionally changing their votes to swindle the results. Admittedly, the slope is too steep - but that can be tweaked by changing the value of or even by introducing a separate decay factor .
While this is what you would normally see when conviction voting is used, your use case has different requirements. The conviction score is made to converge asymptotically to a certain value () because normally, when voting in e.g. a DAO, you don't want a small number of people to be able to get a proposal to pass by locking in small amounts of tokens and waiting for a long time. The convergence prevents that: regardless of how long you wait, the maximum weight of your vote is capped. In this case you want to reward users for keeping their tokens locked for a longer time - capping the score is not what you want.
Let's tweak the formula then.
We don't need anything complicated, we can define an arbitrarily small conviction factor, let's say and use it to calculate a score like this:
If we assume an initial vote , our conviction curve looks like this:
On the surface this looks great, except that it reintroduces a problem: a token-rich voter is now capable of influencing the results. How can that be prevented?
We can take inspiration from quadratic voting.
Quadratic Voting's comeback
Remember in the beginning of this article when I said that quadratic voting wasn't good enough for this use case? That is true when used by itself, but we can make good use of it here.
The idea is simple: in the second term of the formula we replace the vote, , with .
If you want, you can make time even more relevant by ignoring the initial vote, like this:
I'm not going to drop another graph here, I don't think it's necessary, you got the idea. We can then sum all the scores, and square them:
The final formula and conclusion
We are almost done, there are a couple of things missing.
First, we are implicitly assuming that all votes are cast at the same time - which is unlikely. Let's define as:
Where is the time the vote was cast.
Then all we need is to reintroduce the decay. For a given proposal, the final score is thus:
And there you have it. A formula for...Quadratic Conviction Voting, I guess? 🤔
Here's what that looks like with a single voter in RStudio:
And here's what a scenario with simulated voters looks like - the scores for this have been calculated on-chain by a Solidity contract:
There are a limited set of use cases where this will be useful, like intent signalling in time bound voting scenarios. Nevertheless, I hope you learned something from this little journey.