Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daokit): add govdao threshold condition #1496

Merged
merged 9 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions gno/p/daocond/cond_govdao.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package daocond

import (
"errors"

"gno.land/p/demo/json"
"gno.land/p/demo/ufmt"
)

func GovDaoCondThreshold(threshold float64, hasRoleFn func(memberId string, role string) bool, usersWithRoleCountFn func(role string) uint64) Condition {
if threshold <= 0 || threshold > 1 {
panic(errors.New("invalid threshold"))
}
if usersWithRoleCountFn == nil {
panic(errors.New("nil usersWithRoleCountFn"))
}
if hasRoleFn == nil {
panic(errors.New("nil hasRoleFn"))
}
return &govDaoCondThreshold{
threshold: threshold,
hasRoleFn: hasRoleFn,
usersWithRoleCountFn: usersWithRoleCountFn,
}
}

type govDaoCondThreshold struct {
threshold float64
hasRoleFn func(memberId string, role string) bool
usersWithRoleCountFn func(role string) uint64
}

func (m *govDaoCondThreshold) NewState() State {
return &govDaoCondThresholdState {
cond: m,
}
}

func (m *govDaoCondThreshold) Render() string {
return ufmt.Sprintf("%g%% of members", m.threshold*100)
}

func (m *govDaoCondThreshold) RenderJSON() *json.Node {
return json.ObjectNode("", map[string]*json.Node{
"type": json.StringNode("", "govdao-threshold"),
"threshold": json.NumberNode("", m.threshold),
})
}

var _ Condition = (*govDaoCondThreshold)(nil)

type govDaoCondThresholdState struct {
cond *govDaoCondThreshold
}

func (m *govDaoCondThresholdState) Eval(votes map[string]Vote) bool{
return m.yesRatio(votes) >= m.cond.threshold
}
func (m *govDaoCondThresholdState) HandleEvent(_ Event, _ map[string]Vote) {
panic(errors.New("not implemented"))
}
func (m *govDaoCondThresholdState) RenderJSON(votes map[string]Vote) *json.Node {
return json.ObjectNode("", map[string]*json.Node{
"type": json.StringNode("", "govdao-threshold"),
"totalYes": json.NumberNode("", m.yesRatio(votes)),
})
}

var _ State = (*govDaoCondThresholdState)(nil)

func (m *govDaoCondThresholdState) yesRatio(votes map[string]Vote) float64 {
var totalYes float64
votingPowersByTier, totalPower := m.computeVotingPowers()
// Case when there are zero T1s
if totalPower == 0.0 {
return totalPower
}

for userID, vote := range votes {
if vote != VoteYes {
continue
}
tier := m.getUserTier(userID)

totalYes += votingPowersByTier[tier]
}
return totalYes/totalPower
}

func (m *govDaoCondThresholdState) getUserTier(userID string) string{
for _,role := range []string{roleT1, roleT2, roleT3}{
if m.cond.hasRoleFn(userID, role){
return role
}
}
panic("No role found for user")
}

func (m *govDaoCondThresholdState) computeVotingPowers() (map[string]float64, float64) {
totalT1s := float64(m.cond.usersWithRoleCountFn(roleT1))
totalT2s := float64(m.cond.usersWithRoleCountFn(roleT2))
totalT3s := float64(m.cond.usersWithRoleCountFn(roleT3))
votingPowers := map[string]float64{
roleT1: 3.0,
roleT2: computePower(totalT1s, totalT2s, 2.0),
roleT3: computePower(totalT1s, totalT3s, 1.0),
}

totalPower := votingPowers[roleT1]*totalT1s + votingPowers[roleT2]*totalT2s + votingPowers[roleT3]*totalT3s

return votingPowers, totalPower
}

// max power here is the number of votes each tier gets when we have
// the same number of member on each tier
// T2 = 2.0 and T1 = 1.0 with the ration T1/Tn
// we compute the actual ratio
func computePower(T1, Tn, maxPower float64) float64{
// If there are 0 Tn (T2, T3) just return the max power
// we could also return 0.0 as voting power
if Tn <= 0.0{
return maxPower
}

computedPower := (T1/Tn)*maxPower
if computedPower >= maxPower{
// If computed power is bigger than the max, this happens if Tn is lower than T1
// cap the max power to max power.
return maxPower
}
return computedPower
}

const (
roleT1= "T1"
roleT2= "T2"
roleT3= "T3"
)
Loading