5 min read

Conviction Voting

Conviction Voting

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?

Quadratic Voting

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.

$$cost = numVotes^2$$

Here's what that looks like:

Number of Votes Cost
1 1
2 4
3 9
4 16
5 25

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.

Conviction Voting

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:

$$y_t = \alpha^t y_0 + x \sum_{i=0}^{t-1} \alpha^{i}$$

Where \(x\) is the initial cost of the vote, and \(y_0\) is the score at the time the vote was withdrawn.

Which is equivalent to:

$$y_t = \alpha^t y_0 + x \frac{1-\alpha^t}{1-\alpha}$$

Since \(\alpha\) is a decay factor, meaning that \(0 < \alpha < 1\), the first term is an exponential decay function. The second term is a function that converges asymptotically to \(\frac{x}{1 - \alpha}\):

$$\lim_{t \to +\infty} x \frac{1-\alpha^t}{1-\alpha}= \frac{x}{1-\alpha}$$

Given all the above, what does the conviction curve look like? Assuming \(y_0 = 0\), we get that \(y_t = x \frac{1 - \alpha^t}{1 - \alpha}\). With e.g. \(\alpha = 0.9\) and \(x = 2\), our conviction curve will look like this:

Conviction Curve

If we instead assume \(x = 0\) (meaning the vote was withdrawn), we get \(y_t = \alpha^t y_0\), which by itself looks like this (with \(\alpha = 0.9\) and \(y_0 = 20\)):

Decay Function

We can now combine the two, which gives us this:

Conviction Voting with Decay

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 \(\alpha\) or even by introducing a separate decay factor \(\lambda\).

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 (\(\frac{x}{1 - \alpha}\)) 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 \(\epsilon = 0.001\) and use it to calculate a score like this:

$$y_t = x + x \epsilon t^2$$

If we assume an initial vote \(x = 2\), our conviction curve looks like this:

Exponential Conviction

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 \(y_t = x + x \epsilon t^2\) we replace the vote, \(x\), with \(\sqrt[2]{x}\).

$$y_t = x + \sqrt[2]{x} \epsilon t^2$$

If you want, you can make time even more relevant by ignoring the initial vote, like this:

$$y_t = \sqrt[2]{x} \epsilon t^2$$

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:

$$y_t = (\sum_{i=0}^{t-1} \sqrt[2]{x_i} \epsilon t_i^2)^2$$

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 \(\Delta t\) as:

$$\Delta t = t - t_{0i}$$

Where \(t_{0i}\) 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:

$$y_t = \sum_{i=0}^{n-1}\alpha^{\Delta t} y_{i0} + (\sum_{i=0}^{n-1} \sqrt[2]{x_i} \epsilon {\Delta t}_i^2)^2$$

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:

Quadratic Conviction Voting

And here's what a scenario with simulated voters looks like - the scores for this have been calculated on-chain by a Solidity contract:

Quadratic Conviction Voting Example

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.