Quadratic Funding (QF) is a method to make funding more democratic, in any system where there is a group of people that are interested in a set of proposals, and they all have varying amounts of voting power/money that they are contributing. It makes it more democratic by biasing the funding towards proposals that have a higher number of supporters for it, instead of simply summing the voting power/money each proposal gets and letting that decide, because otherwise one wealthy/influential person could dictate where most of the funding was distributed, instead of the voice of the majority.
QF can have different shapes depending on the who, what, when, where, and why. For example, on Gitcoin, they have funding rounds where the public can donate to certain projects, and then there is a separate pool of money coming from sponsors like the Ethereum Foundation, which gets distributed to the projects based on how the public donated to these projects. The public’s donations can be seen as votes, where the more votes a project received, the more matching it got from the sponsor pool. But crucially, the function that determines how much matching a project received is biased towards projects that had a higher number of individual donors to it, and not simply the amount of money (voting power) it got, which could come from a single person.
In the case of designing Ren’s QF rounds, we already have every Darknode Operator (DNO) providing to the Community Fund, and we already have established DNOs as those with voting power in RIP-000-004, where the voting power also nicely tracks how much a DNO has been contributing to the Community Fund.
So we can simply let DNOs distribute their voting power towards the proposals they support in a Funding Round, and have that dictate how much funding the different proposals receive (with some caveats)! Very elegant.
Below is a proposal for how Quadratic Funding could take place at Ren, with concrete numbers to provide some intuition for how it could turn out in reality. The numbers are made up but ‘realistic’ and designed to be educational. I am also including how it would look like if people were voting randomly, as well as how it would look like if there were a few very popular proposals that most people voted for. And for both of those cases I’ll be showing how the amount of capital in the funding pool would influence the funding for the proposals.
So how does the QF algorithm work:
The QF algorithm works very simply like this:
What you are left with then is a number you can convert to a percentage that it should get from the funding pool.
Here is a simplified example:
## vote voter proposal
## 1 5 1 1
## 2 3 1 2
## 3 2 1 3
## 4 6 2 1
## 5 10 2 1
## 6 4 3 1
## 7 36 3 3
Take the square root:
example$vote <- sqrt(example$vote) # take the square root of every vote
## vote voter proposal
## 1 2.236068 1 1
## 2 1.732051 1 2
## 3 1.414214 1 3
## 4 2.449490 2 1
## 5 3.162278 2 1
## 6 2.000000 3 1
## 7 6.000000 3 3
Combine (sum) the votes for each proposal:
## proposal combinedVotingPower
## 1 1 9.847836
## 2 2 1.732051
## 3 3 7.414214
Square out the values again:
example$combinedVotingPower <- (example$combinedVotingPower)^2 # square the values
## proposal combinedVotingPower
## 1 1 96.979874
## 2 2 3.000001
## 3 3 54.970569
Then you can convert these to percentages:
## proposal combinedVotingPower
## 1 1 0.62587671
## 2 2 0.01936103
## 3 3 0.35476226
You can also compare what would happen if you simply summed the votes, so ignoring QF, and then converting to percentages:
## proposal combinedVotingPower simpleVotingInstead
## 1 1 0.62587671 0.37878788
## 2 2 0.01936103 0.04545455
## 3 3 0.35476226 0.57575758
Compared to simply summing votes, QF gave proposal 1 more weight because there were more people voting for it, and it made proposal 2 less interesting as only one person voted for it while the others had multiple. And proposal 3 had a whale voting for it and would get most funding in a simple funding round, but with QF proposal 1 got more.
So let’s imagine some realistic proposals:
id <- 0:10
target <- c(0, 500, 2000, 3500, 4400, 5000, 9000, 10000, 12000, 18000, 25000) # in USD
min <- target/2
max <- target*2
proposals <- data.frame(id, target, min, max)
proposals
## id target min max
## 1 0 0 0 0
## 2 1 500 250 1000
## 3 2 2000 1000 4000
## 4 3 3500 1750 7000
## 5 4 4400 2200 8800
## 6 5 5000 2500 10000
## 7 6 9000 4500 18000
## 8 7 10000 5000 20000
## 9 8 12000 6000 24000
## 10 9 18000 9000 36000
## 11 10 25000 12500 50000
The first thing you might notice is why are there 11 proposals, not 10? This is because we need a way for DNOs to vote ‘No’, as in ‘I do not want that funding should be going to any of the proposals in this round’.
Second, you might notice that there is something called a target, and a min and a max as well. This is a design decision we are proposing, that makes sure that money is not wasted on a proposal that is unable to lift of the ground, as most projects need a certain amount of funding to be viable. The max is for putting a ceiling on the max amount we can give to a proposal, so we are not overspending on something that only needs a certain amount of funding to work as intended. Here specifically we’ve chosen a range that is half of the target, to twice the target. It means that any proposer will need to provide a target number in the proposal.
You might also notice that targets are not specified in BTC. For the voting outcome not to shift throughout the voting period simply because of price volatility, we also propose that targets are specified in USD and that the Community Fund exchanges some of its assets into a stablecoin like DAI before the Funding Round, and distribute the grants in that stablecoin.
Imagine some voters with their voting powers:
nVoters <- 100
votingPower <- rbeta(nVoters, 1, 4)*40 # random voting power values that are similar to current DNO voting powers
votingPower
## [1] 11.381434254 13.109636428 0.007316686 6.446772000 14.787482442
## [6] 4.578120316 6.906559577 11.437504536 3.342890778 13.195024792
## [11] 3.289363040 15.405751659 4.895058691 3.354535938 1.104670929
## [16] 5.226015058 8.553705977 2.339464558 2.728144939 29.228919039
## [21] 18.485324714 2.692869923 19.264901024 0.304212030 3.095006243
## [26] 6.773281315 1.982136306 21.735177219 3.997641143 8.551554346
## [31] 7.333546587 11.377955306 13.844816869 8.004629019 12.568894072
## [36] 0.330210848 18.747441552 4.382342126 0.388400826 0.603477215
## [41] 14.842017250 3.983426272 0.520764064 6.598891343 0.957389998
## [46] 3.192134176 16.373225807 9.813951536 6.729539252 4.138074338
## [51] 3.437872530 20.990387929 6.835689193 1.538328327 0.142624239
## [56] 19.441895297 6.617161300 15.238640596 3.366923640 21.051880536
## [61] 7.774297272 12.415112232 5.062479396 7.956076543 13.767410726
## [66] 6.152018468 8.132727189 7.863869698 0.848843178 6.860341031
## [71] 3.236693887 7.860096652 3.413806575 6.441228732 6.977800437
## [76] 26.854311139 11.259198287 0.104366569 18.713945789 5.642455440
## [81] 12.235018527 3.488482176 16.508668610 4.476638383 3.726026650
## [86] 18.288356195 1.167020704 0.847463896 3.524267583 10.630716516
## [91] 4.147342959 10.439263424 3.893990809 17.256288463 8.510819039
## [96] 16.080248619 13.625663496 8.226829998 5.111318110 9.102187500
Now, since we’ll be using the Snapshot mechanism for voting, and specifically the Scattershot fork as it allows for multiple-choice voting, the way people assign voting power is clicking on the proposals as many times as you want to divide up your voting power:
You can test yourself here: https://scattershot.page/#/ren-project.eth/proposal/QmXFZaWC8uBUMXNTgGyib7YL6QyDWBSVWoTV62kPB6z1DP
So let’s start with the case where people would just be voting randomly:
(if you want to see the code for this, check out the markdown file)
## voterID votingIDPower voteForProp votingPowerFrac
## 1 1 11.381434254 10 2.845358563
## 2 1 11.381434254 3 2.845358563
## 3 1 11.381434254 4 2.845358563
## 4 1 11.381434254 11 2.845358563
## 5 2 13.109636428 8 6.554818214
## 6 2 13.109636428 10 6.554818214
## 7 3 0.007316686 9 0.002438895
## 8 3 0.007316686 5 0.002438895
## 9 3 0.007316686 9 0.002438895
## 10 4 6.446772000 8 1.289354400
…
## voterID votingIDPower voteForProp votingPowerFrac
## 392 97 13.625663 10 3.4064159
## 393 97 13.625663 10 3.4064159
## 394 98 8.226830 2 8.2268300
## 395 99 5.111318 4 0.8518864
## 396 99 5.111318 6 0.8518864
## 397 99 5.111318 3 0.8518864
## 398 99 5.111318 2 0.8518864
## 399 99 5.111318 11 0.8518864
## 400 99 5.111318 8 0.8518864
## 401 100 9.102188 1 9.1021875
As you see there, we have a bunch of rows of votes on different proposals with the voting power amount, and this is similar to the data anyone could grab from Scattershot after the vote is complete.
After doing the QF process, here are the results for our 10 proposals, including the percentages:
## id target min max QVP fundPercent
## 1 0 0 0 0 1349.016 0.06592356
## 2 1 500 250 1000 2189.864 0.10701405
## 3 2 2000 1000 4000 1615.918 0.07896650
## 4 3 3500 1750 7000 2155.678 0.10534347
## 5 4 4400 2200 8800 1072.804 0.05242566
## 6 5 5000 2500 10000 2212.172 0.10810421
## 7 6 9000 4500 18000 2588.826 0.12651050
## 8 7 10000 5000 20000 2262.595 0.11056827
## 9 8 12000 6000 24000 1234.810 0.06034255
## 10 9 18000 9000 36000 2306.595 0.11271848
## 11 10 25000 12500 50000 1475.053 0.07208275
With those funding percentages per proposal, we can now see how much of the funding pool has been assigned to each proposal. But the proposal’s target will affect if it actually gets funded, and how much money is in the funding pool. If there is little money in the pool, the grant might not get passed the ‘min’ value, and in that case it won’t get funding.
So imagine we have 3 different scenarios:
## id target min max QVP fundPercent poorPoolAlloc poorPoolPASS
## 1 0 0 0 0 1349.016 0.06592356 1964.522 TRUE
## 2 1 500 250 1000 2189.864 0.10701405 3189.019 TRUE
## 3 2 2000 1000 4000 1615.918 0.07896650 2353.202 TRUE
## 4 3 3500 1750 7000 2155.678 0.10534347 3139.235 TRUE
## 5 4 4400 2200 8800 1072.804 0.05242566 1562.285 FALSE
## 6 5 5000 2500 10000 2212.172 0.10810421 3221.506 TRUE
## 7 6 9000 4500 18000 2588.826 0.12651050 3770.013 FALSE
## 8 7 10000 5000 20000 2262.595 0.11056827 3294.935 FALSE
## 9 8 12000 6000 24000 1234.810 0.06034255 1798.208 FALSE
## 10 9 18000 9000 36000 2306.595 0.11271848 3359.011 FALSE
## 11 10 25000 12500 50000 1475.053 0.07208275 2148.066 FALSE
If there isn’t much money in the pool, and people vote randomly, likely only the proposals asking for small grants will get funded.
## id target min max QVP fundPercent matchPoolAlloc matchPoolPASS
## 1 0 0 0 0 1349.016 0.06592356 5893.567 TRUE
## 2 1 500 250 1000 2189.864 0.10701405 9567.056 TRUE
## 3 2 2000 1000 4000 1615.918 0.07896650 7059.605 TRUE
## 4 3 3500 1750 7000 2155.678 0.10534347 9417.706 TRUE
## 5 4 4400 2200 8800 1072.804 0.05242566 4686.854 TRUE
## 6 5 5000 2500 10000 2212.172 0.10810421 9664.517 TRUE
## 7 6 9000 4500 18000 2588.826 0.12651050 11310.038 TRUE
## 8 7 10000 5000 20000 2262.595 0.11056827 9884.804 TRUE
## 9 8 12000 6000 24000 1234.810 0.06034255 5394.624 FALSE
## 10 9 18000 9000 36000 2306.595 0.11271848 10077.032 TRUE
## 11 10 25000 12500 50000 1475.053 0.07208275 6444.198 FALSE
If there is matched money in the pool, and people vote randomly, the proposals asking for a lot of money still might not pass their min ask, and then won’t get funded.
## id target min max QVP fundPercent richPoolAlloc richPoolPASS
## 1 0 0 0 0 1349.016 0.06592356 17680.70 TRUE
## 2 1 500 250 1000 2189.864 0.10701405 28701.17 TRUE
## 3 2 2000 1000 4000 1615.918 0.07896650 21178.82 TRUE
## 4 3 3500 1750 7000 2155.678 0.10534347 28253.12 TRUE
## 5 4 4400 2200 8800 1072.804 0.05242566 14060.56 TRUE
## 6 5 5000 2500 10000 2212.172 0.10810421 28993.55 TRUE
## 7 6 9000 4500 18000 2588.826 0.12651050 33930.11 TRUE
## 8 7 10000 5000 20000 2262.595 0.11056827 29654.41 TRUE
## 9 8 12000 6000 24000 1234.810 0.06034255 16183.87 TRUE
## 10 9 18000 9000 36000 2306.595 0.11271848 30231.10 TRUE
## 11 10 25000 12500 50000 1475.053 0.07208275 19332.59 TRUE
If there is a lot of money in the pool, and people vote randomly, it’s likely that all proposals will be funded.
## id target min max QVP fundPercent poorPoolAlloc poorPoolPASS
## 1 0 0 0 0 12377.294607 0.3912051828 11657.914448 TRUE
## 2 1 500 250 1000 52.634128 0.0016635900 49.574982 FALSE
## 3 2 2000 1000 4000 196.639552 0.0062151233 185.210674 FALSE
## 4 3 3500 1750 7000 6.022041 0.0001903367 5.672034 FALSE
## 5 4 4400 2200 8800 153.640809 0.0048560758 144.711058 FALSE
## 6 5 5000 2500 10000 143.001323 0.0045197970 134.689950 FALSE
## 7 6 9000 4500 18000 10345.118013 0.3269748287 9743.849895 TRUE
## 8 7 10000 5000 20000 339.050363 0.0107162561 319.344433 FALSE
## 9 8 12000 6000 24000 126.833762 0.0040087941 119.462063 FALSE
## 10 9 18000 9000 36000 168.932698 0.0053394016 159.114168 FALSE
## 11 10 25000 12500 50000 7729.714679 0.2443106139 7280.456295 FALSE
If there isn’t much money in the pool, the popular proposals are much more likely to pass, but not the others.
## id target min max QVP fundPercent matchPoolAlloc matchPoolPASS
## 1 0 0 0 0 12377.294607 0.3912051828 34973.7433 TRUE
## 2 1 500 250 1000 52.634128 0.0016635900 148.7249 FALSE
## 3 2 2000 1000 4000 196.639552 0.0062151233 555.6320 FALSE
## 4 3 3500 1750 7000 6.022041 0.0001903367 17.0161 FALSE
## 5 4 4400 2200 8800 153.640809 0.0048560758 434.1332 FALSE
## 6 5 5000 2500 10000 143.001323 0.0045197970 404.0699 FALSE
## 7 6 9000 4500 18000 10345.118013 0.3269748287 29231.5497 TRUE
## 8 7 10000 5000 20000 339.050363 0.0107162561 958.0333 FALSE
## 9 8 12000 6000 24000 126.833762 0.0040087941 358.3862 FALSE
## 10 9 18000 9000 36000 168.932698 0.0053394016 477.3425 FALSE
## 11 10 25000 12500 50000 7729.714679 0.2443106139 21841.3689 TRUE
If there is matched money in the pool, the popular proposals are still much more likely to pass and inhibits the chance that the unpopular proposals get funded.
## id target min max QVP fundPercent richPoolAlloc richPoolPASS
## 1 0 0 0 0 12377.294607 0.3912051828 104921.23003 TRUE
## 2 1 500 250 1000 52.634128 0.0016635900 446.17484 TRUE
## 3 2 2000 1000 4000 196.639552 0.0062151233 1666.89606 TRUE
## 4 3 3500 1750 7000 6.022041 0.0001903367 51.04831 FALSE
## 5 4 4400 2200 8800 153.640809 0.0048560758 1302.39953 FALSE
## 6 5 5000 2500 10000 143.001323 0.0045197970 1212.20955 FALSE
## 7 6 9000 4500 18000 10345.118013 0.3269748287 87694.64906 TRUE
## 8 7 10000 5000 20000 339.050363 0.0107162561 2874.09989 FALSE
## 9 8 12000 6000 24000 126.833762 0.0040087941 1075.15857 FALSE
## 10 9 18000 9000 36000 168.932698 0.0053394016 1432.02751 FALSE
## 11 10 25000 12500 50000 7729.714679 0.2443106139 65524.10665 TRUE
If there is a lot of money in the pool, the popular proposals will still inhibit the unpopular proposals to get funding.
So QF is good at filtering out spam on its own, we don’t need to manually do that beforehand.
As we have seen, QF goes for the popular votes, and inhibits influence from whales, and ignores proposals that only one of a few people vote for.
This is a point to consider. If there are a lot of competing proposals, and they ask for a lot of funding relative to what the Community Ecosystem Fund has allocated to the funding round, there is a chance the proposals will inhibit each others in the competition, and might mean none of them get any. But in a rich environment, popular proposals are likely to get funded.
A remaining question I still have is:
Would appreciate any feedback on this question and the whole model overall, and critiques if you have any!