cond_gnolovedao.gno
4.56 Kb ยท 148 lines
1package daocond
2
3import (
4 "errors"
5 "math"
6 "strconv"
7 "strings"
8
9 "gno.land/p/nt/ufmt"
10)
11
12type gnolovDaoCondThreshold struct {
13 threshold float64
14 roles []string
15 hasRoleFn func(memberId string, role string) bool
16 usersWithRoleCountFn func(role string) uint32
17}
18
19var roleWeights = []float64{3.0, 2.0, 1.0}
20
21// This is our implementation of the govdao codition following Jae Kwon's specification: https://gist.github.com/jaekwon/918ad325c4c8f7fb5d6e022e33cb7eb3
22func GnoloveDAOCondThreshold(threshold float64, roles []string, hasRoleFn func(memberId string, role string) bool, usersWithRoleCountFn func(role string) uint32) Condition {
23 if threshold <= 0 || threshold > 1 {
24 panic(errors.New("invalid threshold"))
25 }
26 if usersWithRoleCountFn == nil {
27 panic(errors.New("nil usersWithRoleCountFn"))
28 }
29 if hasRoleFn == nil {
30 panic(errors.New("nil hasRoleFn"))
31 }
32 if len(roles) > 3 {
33 panic("the gnolove dao condition handles at most 3 roles")
34 }
35 return &gnolovDaoCondThreshold{
36 threshold: threshold,
37 roles: roles,
38 hasRoleFn: hasRoleFn,
39 usersWithRoleCountFn: usersWithRoleCountFn,
40 }
41}
42
43// Eval implements Condition.
44func (c *gnolovDaoCondThreshold) Eval(ballot Ballot) bool {
45 return c.yesRatio(ballot) >= c.threshold
46}
47
48// Signal implements Condition.
49func (c *gnolovDaoCondThreshold) Signal(ballot Ballot) float64 {
50 return math.Min(c.yesRatio(ballot)/c.threshold, 1)
51}
52
53// Render implements Condition.
54func (c *gnolovDaoCondThreshold) Render() string {
55 rolePowers := []string{}
56 for i, role := range c.roles {
57 weight := strconv.FormatFloat(roleWeights[i], 'f', 2, 64) // ufmt.Sprintf("%.2f", ...) is not working
58 rolePowers = append(rolePowers, ufmt.Sprintf("%s => %s power", role, weight))
59 }
60 return ufmt.Sprintf("%g%% of total voting power | %s", c.threshold*100, strings.Join(rolePowers, " | "))
61}
62
63// RenderWithVotes implements Condition.
64func (c *gnolovDaoCondThreshold) RenderWithVotes(ballot Ballot) string {
65 vPowers, totalPower := c.computeVotingPowers()
66 rolePowers := []string{}
67 for _, role := range c.roles {
68 weight := strconv.FormatFloat(vPowers[role], 'f', 2, 64) // ufmt.Sprintf("%.2f", ...) is not working
69 rolePowers = append(rolePowers, ufmt.Sprintf("%s => %s power", role, weight))
70 }
71 s := ""
72 s += ufmt.Sprintf("%g%% of total voting power | %s\n\n", c.threshold*100, strings.Join(rolePowers, " | "))
73 s += ufmt.Sprintf("Threshold needed: %g%% of total voting power\n\n", c.threshold*100)
74 s += ufmt.Sprintf("Yes: %d/%d\n\n", c.yesRatio(ballot), totalPower)
75 s += ufmt.Sprintf("Voting power needed: %g%% of total voting power\n\n", c.threshold*totalPower)
76 return s
77}
78
79var _ Condition = (*gnolovDaoCondThreshold)(nil)
80
81func (c *gnolovDaoCondThreshold) yesRatio(ballot Ballot) float64 {
82 var totalYes float64
83 votingPowersByTier, totalPower := c.computeVotingPowers()
84 // Case when there are zero T1s
85 if totalPower == 0.0 {
86 return totalPower
87 }
88
89 ballot.Iterate(func(voter string, vote Vote) bool {
90 if vote != VoteYes {
91 return false
92 }
93 tier := c.getUserRole(voter)
94 totalYes += votingPowersByTier[tier]
95 return false
96 })
97 return totalYes / totalPower
98}
99
100func (c *gnolovDaoCondThreshold) getUserRole(userID string) string {
101 for _, role := range c.roles {
102 if c.hasRoleFn(userID, role) {
103 return role
104 }
105 }
106 panic("No role found for user")
107}
108
109func (c *gnolovDaoCondThreshold) computeVotingPowers() (map[string]float64, float64) {
110 votingPowers := make(map[string]float64)
111 totalPower := 0.0
112 countsMembersPerRole := make(map[string]float64)
113
114 for _, role := range c.roles {
115 countsMembersPerRole[role] = float64(c.usersWithRoleCountFn(role))
116 }
117
118 for i, role := range c.roles {
119 if i == 0 {
120 votingPowers[role] = roleWeights[0] // Highest tier always gets max power (3.0)
121 } else {
122 votingPowers[role] = computePower(countsMembersPerRole[c.roles[0]], countsMembersPerRole[role], roleWeights[i])
123 }
124 totalPower += votingPowers[role] * countsMembersPerRole[role]
125 }
126
127 return votingPowers, totalPower
128}
129
130// max power here is the number of votes each tier gets when we have
131// the same number of member on each tier
132// T2 = 2.0 and T1 = 1.0 with the ration T1/Tn
133// we compute the actual ratio
134func computePower(T1, Tn, maxPower float64) float64 {
135 // If there are 0 Tn (T2, T3) just return the max power
136 // we could also return 0.0 as voting power
137 if Tn <= 0.0 {
138 return maxPower
139 }
140
141 computedPower := (T1 / Tn) * maxPower
142 if computedPower >= maxPower {
143 // If computed power is bigger than the max, this happens if Tn is lower than T1
144 // cap the max power to max power.
145 return maxPower
146 }
147 return computedPower
148}