-
Notifications
You must be signed in to change notification settings - Fork 0
/
report.Rmd
442 lines (275 loc) · 15.2 KB
/
report.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
---
title: "Ren Quadratic Funding Rounds"
author: "Maximilian Roszko"
date: '2021-05-18'
output: html_document
---
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](https://www.gitcoin.co), 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](https://forum.renproject.io/t/rip-000-004-add-snapshot-as-a-signaling-mechanism-for-rips/686), 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:
- Take the square root (√) of every vote. So if I have 10 voting power, and I assign 5 VP to one proposal, and 3 VP to another and 2 VP to a third one, those are three different votes, so each one is square rooted separately
- For each proposal, sum up all the votes for it (votes which have been square rooted)
- Then you square (^2) the summed up values for each proposal
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:
```{r include=FALSE}
vote <- c(5, 3, 2, 6, 10, 4, 36)
voter <- c(1, 1, 1, 2, 2, 3, 3)
proposal <- c(1, 2, 3, 1, 1, 1, 3)
example <- data.frame(vote, voter, proposal)
```
```{r echo=FALSE}
example
```
Take the square root:
```{r}
example$vote <- sqrt(example$vote) # take the square root of every vote
```
```{r echo=FALSE}
example
```
```{r include=FALSE}
proposal <- c(1, 2, 3)
combinedVotingPower <- c(9.847836, 1.732051, 7.414214)
example <- data.frame(proposal, combinedVotingPower)
```
Combine (sum) the votes for each proposal:
```{r echo=FALSE}
example
```
Square out the values again:
```{r}
example$combinedVotingPower <- (example$combinedVotingPower)^2 # square the values
```
```{r echo=FALSE}
example
```
Then you can convert these to percentages:
```{r echo=FALSE}
example$combinedVotingPower <- (example$combinedVotingPower)/sum(example$combinedVotingPower) # square the values
example
```
You can also compare what would happen if you simply summed the votes, so ignoring QF, and then converting to percentages:
```{r echo=FALSE}
example$simpleVotingInstead <- c(25/66, 3/66, 38/66)# square the values
example
```
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:**
```{r}
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
```
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:**
```{r}
nVoters <- 100
votingPower <- rbeta(nVoters, 1, 4)*40 # random voting power values that are similar to current DNO voting powers
votingPower
```
---
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:
![](C:/scattershot_voting.PNG)
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](https://github.com/MaximilianR/quadratic-funding-rounds/blob/main/report.Rmd))
```{r echo=FALSE, message=FALSE, warning=FALSE}
voterVotingSplit <- rpois(100, 4) # a poisson draw gives whole numbers which is useful in this case
voterVotingSplit[voterVotingSplit == 0] <- 1 # make sure that a voter at least votes for one thing
voterID <- 0
votingIDPower <- 0
voteForProp <- 0
votingPowerFrac <- 0
votingResults <- data.frame(voterID, votingIDPower, voteForProp, votingPowerFrac)
# voting algo
for (voter in 1:nVoters){
for (cast in 1:voterVotingSplit[voter]){
voterID <- voter
votingIDPower <- votingPower[voter]
voteForProp <- sample(1:11, 1)
votingPowerFrac <- votingPower[voter]/voterVotingSplit[voter]
tempDF <- data.frame(voterID, votingIDPower, voteForProp, votingPowerFrac)
votingResults <- rbind(votingResults, tempDF)
}
}
votingResults <- votingResults[-1,]
rownames(votingResults) <- NULL
head(votingResults, 10)
```
...
```{r echo=FALSE}
tail(votingResults, 10)
```
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:**
```{r echo=FALSE, message=FALSE, warning=FALSE}
library(magrittr)
library(tidyverse)
# in the data frame people have sometimes multiple votes per proposal because of how I constructed the simulation, so I have to fix this first by combining them before doing the QF, otherwise that biases things incorrectly
sqrtVotes <- votingResults %>%
group_by(voterID, voteForProp) %>%
summarise(sqrtVote = sqrt(sum(votingPowerFrac)))
proposals$QVP <- 0
for (prop in 1:11) {
proposals[prop,]$QVP <- sum(sqrtVotes[sqrtVotes$voteForProp == prop,]$sqrtVote)^2
}
proposals$fundPercent <- 0
for (prop in 1:11) {
proposals[prop,]$fundPercent <- proposals[prop,]$QVP / sum(proposals$QVP)
}
proposals
```
**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:
- Our funding pool is poor (it has less than a third of the money than all the proposals' targets combined)
- There is as much money in the funding pool as the proposals ask for combined
- There is 3x as much money as the proposals ask for combined
```{r include=FALSE}
proposals$poorPoolAlloc <- 0
proposals$poorPoolPASS <- FALSE
poorPool <- sum(target)/3
matched <- sum(target)
richPool <- sum(target)*3
for (prop in 1:11) {
proposals[prop,]$poorPoolAlloc <- poorPool * proposals[prop,]$fundPercent
proposals[prop,]$poorPoolPASS <- ifelse(proposals[prop,]$poorPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**If there isn't much money in the pool, and people vote randomly, likely only the proposals asking for small grants will get funded.**
```{r include=FALSE}
proposals$poorPoolAlloc <- NULL
proposals$poorPoolPASS <- NULL
proposals$matchPoolAlloc <- 0
proposals$matchPoolPASS <- FALSE
for (prop in 1:11) {
proposals[prop,]$matchPoolAlloc <- matched * proposals[prop,]$fundPercent
proposals[prop,]$matchPoolPASS <- ifelse(proposals[prop,]$matchPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**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.**
```{r include=FALSE}
proposals$matchPoolAlloc <- NULL
proposals$matchPoolPASS <- NULL
proposals$richPoolAlloc <- 0
proposals$richPoolPASS <- FALSE
for (prop in 1:11) {
proposals[prop,]$richPoolAlloc <- richPool * proposals[prop,]$fundPercent
proposals[prop,]$richPoolPASS <- ifelse(proposals[prop,]$richPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**If there is a lot of money in the pool, and people vote randomly, it's likely that all proposals will be funded.**
---
### If there are a few popular proposals (0, 6, and 10)
```{r echo=FALSE, message=FALSE, warning=FALSE}
voterVotingSplit <- rpois(100, 3) # a poisson draw gives whole numbers which is useful in this case
voterVotingSplit[voterVotingSplit == 0] <- 1 # make sure that a voter at least votes for one thing
voterID <- 0
votingIDPower <- 0
voteForProp <- 0
votingPowerFrac <- 0
votingResults <- data.frame(voterID, votingIDPower, voteForProp, votingPowerFrac)
# voting algo
for (voter in 1:nVoters){
for (cast in 1:voterVotingSplit[voter]){
voterID <- voter
votingIDPower <- votingPower[voter]
voteForProp <- sample(1:11, 1, prob = c(10,1,1,1,1,1,10,1,1,1,10))
votingPowerFrac <- votingPower[voter]/voterVotingSplit[voter]
tempDF <- data.frame(voterID, votingIDPower, voteForProp, votingPowerFrac)
votingResults <- rbind(votingResults, tempDF)
}
}
votingResults <- votingResults[-1,]
rownames(votingResults) <- NULL
```
```{r echo=FALSE, message=FALSE, warning=FALSE}
sqrtVotes <- votingResults %>%
group_by(voterID, voteForProp) %>%
summarise(sqrtVote = sqrt(sum(votingPowerFrac)))
proposals$QVP <- 0
for (prop in 1:11) {
proposals[prop,]$QVP <- sum(sqrtVotes[sqrtVotes$voteForProp == prop,]$sqrtVote)^2
}
proposals$fundPercent <- 0
for (prop in 1:11) {
proposals[prop,]$fundPercent <- proposals[prop,]$QVP / sum(proposals$QVP)
}
```
```{r include=FALSE}
proposals$richPoolAlloc <- NULL
proposals$richPoolPASS <- NULL
proposals$poorPoolAlloc <- 0
proposals$poorPoolPASS <- FALSE
poorPool <- sum(target)/3
matched <- sum(target)
richPool <- sum(target)*3
for (prop in 1:11) {
proposals[prop,]$poorPoolAlloc <- poorPool * proposals[prop,]$fundPercent
proposals[prop,]$poorPoolPASS <- ifelse(proposals[prop,]$poorPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**If there isn't much money in the pool, the popular proposals are much more likely to pass, but not the others.**
```{r include=FALSE}
proposals$poorPoolAlloc <- NULL
proposals$poorPoolPASS <- NULL
proposals$matchPoolAlloc <- 0
proposals$matchPoolPASS <- FALSE
for (prop in 1:11) {
proposals[prop,]$matchPoolAlloc <- matched * proposals[prop,]$fundPercent
proposals[prop,]$matchPoolPASS <- ifelse(proposals[prop,]$matchPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**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.**
```{r include=FALSE}
proposals$matchPoolAlloc <- NULL
proposals$matchPoolPASS <- NULL
proposals$richPoolAlloc <- 0
proposals$richPoolPASS <- FALSE
for (prop in 1:11) {
proposals[prop,]$richPoolAlloc <- richPool * proposals[prop,]$fundPercent
proposals[prop,]$richPoolPASS <- ifelse(proposals[prop,]$richPoolAlloc > proposals[prop,]$min, TRUE, FALSE)
}
```
```{r echo=FALSE}
proposals
```
**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.
---
## Conclusion
- QF makes voting more democratic
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.
- It matters how much money is in the funding pool
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:
- What portion of the Community Ecosystem Fund should be allocated to the funding rounds? Also think in relation to the [Fast Lane proposal](https://forum.renproject.io/t/rfc-000-018-fast-lane-community-ecosystem-fund/743)
Would appreciate any feedback on this question and the whole model overall, and critiques if you have any!