Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}