proposal_members.gno
3.39 Kb · 135 lines
1package commondao
2
3import (
4 "errors"
5 "strings"
6 "time"
7
8 "gno.land/p/moul/addrset"
9 "gno.land/p/moul/md"
10 "gno.land/p/nt/commondao/v0"
11)
12
13// newMembersUpdatePropDefinition creates a new proposal definition for adding/removing DAO members.
14func newMembersUpdatePropDefinition(dao *commondao.CommonDAO, add, remove addrset.Set) membersUpdatePropDefinition {
15 if dao == nil {
16 panic("DAO is required")
17 }
18
19 if dao.Members().Size() == 0 {
20 panic("a DAO with at least one member is required to create member update proposals")
21 }
22
23 if add.Size() == 0 && remove.Size() == 0 {
24 panic("no members were specified to be added or removed")
25 }
26
27 return membersUpdatePropDefinition{
28 dao: dao,
29 toAdd: add,
30 toRemove: remove,
31 }
32}
33
34// membersUpdatePropDefinition defines a proposal type for adding/removing DAO members.
35type membersUpdatePropDefinition struct {
36 dao *commondao.CommonDAO
37 toAdd, toRemove addrset.Set
38}
39
40func (membersUpdatePropDefinition) Title() string { return "Members Update" }
41func (membersUpdatePropDefinition) VotingPeriod() time.Duration { return time.Hour * 24 * 7 }
42
43func (p membersUpdatePropDefinition) Body() string {
44 var b strings.Builder
45
46 if p.toAdd.Size() > 0 {
47 items := make([]string, 0, p.toAdd.Size())
48 p.toAdd.IterateByOffset(0, p.toAdd.Size(), func(addr address) bool {
49 items = append(items, addr.String())
50 return false
51 })
52
53 b.WriteString(md.Paragraph(
54 md.Bold("Members to Add:") + "\n" + md.BulletList(items),
55 ))
56 }
57
58 if p.toRemove.Size() > 0 {
59 items := make([]string, 0, p.toRemove.Size())
60 p.toRemove.IterateByOffset(0, p.toRemove.Size(), func(addr address) bool {
61 items = append(items, addr.String())
62 return false
63 })
64
65 b.WriteString(md.Paragraph(
66 md.Bold("Members to Remove:") + "\n" + md.BulletList(items),
67 ))
68 }
69
70 return b.String()
71}
72
73func (p membersUpdatePropDefinition) Validate() (err error) {
74 members := p.dao.Members()
75
76 p.toAdd.IterateByOffset(0, p.toAdd.Size(), func(addr address) bool {
77 if members.Has(addr) {
78 err = errors.New("address is already a DAO member: " + addr.String())
79 return true
80 }
81 return false
82 })
83
84 if err != nil {
85 return err
86 }
87
88 p.toRemove.IterateByOffset(0, p.toRemove.Size(), func(addr address) bool {
89 if !members.Has(addr) {
90 err = errors.New("address is not a DAO member: " + addr.String())
91 return true
92 }
93 return false
94 })
95
96 return err
97}
98
99func (membersUpdatePropDefinition) Tally(ctx commondao.VotingContext) (bool, error) {
100 // When DAO has one or two members succeed when there is a YES vote, otherwise
101 // tally requires at least three votes to be able to tally by 2/3s super majority
102 if ctx.Members.Size() < 3 {
103 return ctx.VotingRecord.VoteCount(commondao.ChoiceYes) > 0, nil
104 }
105
106 if !commondao.IsQuorumReached(commondao.QuorumTwoThirds, ctx.VotingRecord, ctx.Members) {
107 return false, commondao.ErrNoQuorum
108 }
109
110 c, success := commondao.SelectChoiceBySuperMajority(ctx.VotingRecord, ctx.Members.Size())
111 if success {
112 return c == commondao.ChoiceYes, nil
113 }
114 return false, nil
115}
116
117func (p membersUpdatePropDefinition) Executor() commondao.ExecFunc {
118 return p.execute
119}
120
121func (p membersUpdatePropDefinition) execute(realm) error {
122 members := p.dao.Members()
123
124 p.toAdd.IterateByOffset(0, p.toAdd.Size(), func(addr address) bool {
125 members.Add(addr)
126 return false
127 })
128
129 p.toRemove.IterateByOffset(0, p.toRemove.Size(), func(addr address) bool {
130 members.Remove(addr)
131 return false
132 })
133
134 return nil
135}