proposal_council.gno
6.16 Kb · 196 lines
1package commondao
2
3import (
4 "errors"
5 "strings"
6 "time"
7
8 "gno.land/p/moul/md/v0"
9 "gno.land/p/nt/commondao/v0"
10)
11
12// newCouncilUpdatePropDefinition creates a new proposal definition for
13// adding/removing council members.
14func newCouncilUpdatePropDefinition(dao *commondao.CommonDAO, add, remove []address) councilUpdatePropDefinition {
15 if dao == nil {
16 panic("DAO is required")
17 }
18
19 if len(add) == 0 && len(remove) == 0 {
20 panic("no council members were specified to be added or removed")
21 }
22
23 return councilUpdatePropDefinition{
24 dao: dao,
25 toAdd: add,
26 toRemove: remove,
27 }
28}
29
30// councilUpdatePropDefinition defines a proposal type for adding/removing
31// council members. Adds and removes apply as idempotent set operations, so
32// concurrently passed updates merge in execution order; an update whose
33// final set would empty a non-empty council fails at execution.
34type councilUpdatePropDefinition struct {
35 dao *commondao.CommonDAO
36 toAdd, toRemove []address
37}
38
39func (councilUpdatePropDefinition) Title() string { return "Council Update" }
40func (councilUpdatePropDefinition) VotingPeriod() time.Duration { return time.Hour * 24 * 7 }
41
42// isTrustedMarkdownBody marks Body as self-assembled markdown; the renderer
43// renders it verbatim (embedded addresses are formatted by md helpers).
44func (councilUpdatePropDefinition) isTrustedMarkdownBody() {}
45
46// CapExempt exempts council updates from the active proposals cap: a
47// council member who fills the cap must never be able to block their own
48// removal. Exempt proposals are bounded to one active one per creator.
49func (councilUpdatePropDefinition) CapExempt() {}
50
51// Threshold returns the tally threshold: council self-mutation requires a
52// supermajority.
53func (councilUpdatePropDefinition) Threshold() commondao.Threshold {
54 return commondao.ThresholdSupermajority
55}
56
57func (p councilUpdatePropDefinition) Body() string {
58 var b strings.Builder
59
60 if len(p.toAdd) > 0 {
61 b.WriteString(md.Paragraph(
62 md.Bold("Council Members to Add:") + "\n" + md.BulletList(addrStrings(p.toAdd)),
63 ))
64 }
65
66 if len(p.toRemove) > 0 {
67 b.WriteString(md.Paragraph(
68 md.Bold("Council Members to Remove:") + "\n" + md.BulletList(addrStrings(p.toRemove)),
69 ))
70 }
71
72 return b.String()
73}
74
75func (p councilUpdatePropDefinition) Validate() error {
76 // Membership is intentionally not validated: adds and removes are
77 // idempotent, so updates passed concurrently merge cleanly instead of
78 // failing on addresses that another update already added or removed.
79 for _, a := range p.toAdd {
80 for _, r := range p.toRemove {
81 if a == r {
82 return errors.New("address is added and removed at once: " + a.String())
83 }
84 }
85 }
86 return nil
87}
88
89func (p councilUpdatePropDefinition) Executor() commondao.ExecFunc {
90 return p.execute
91}
92
93func (p councilUpdatePropDefinition) execute(_ int, sub realm) error {
94 return p.dao.UpdateCouncil(p.toAdd, p.toRemove)
95}
96
97// newAncestorCouncilUpdatePropDefinition creates a proposal definition for
98// an ancestor DAO to modify a descendant's council membership
99// (docs/CONSTITUTION.md :1531-1532). It is hosted and voted in the
100// ancestor; ancestry is verified at proposal validation.
101func newAncestorCouncilUpdatePropDefinition(dao, target *commondao.CommonDAO, add, remove []address) ancestorCouncilUpdatePropDefinition {
102 if dao == nil {
103 panic("DAO is required")
104 }
105 if target == nil {
106 panic("target DAO is required")
107 }
108 if len(add) == 0 && len(remove) == 0 {
109 panic("no council members were specified to be added or removed")
110 }
111
112 return ancestorCouncilUpdatePropDefinition{
113 dao: dao,
114 target: target,
115 toAdd: add,
116 toRemove: remove,
117 }
118}
119
120// ancestorCouncilUpdatePropDefinition defines a proposal type for an
121// ancestor DAO to add and/or remove members of a descendant's council.
122// This is the spec's rescue path for a stuck or empty descendant council
123// (:1531-1532): decided by the ancestor's own supermajority, validated as
124// strictly proper ancestry so a DAO can never mutate its own council
125// through this path (that is the self-mutating councilUpdate).
126type ancestorCouncilUpdatePropDefinition struct {
127 dao *commondao.CommonDAO // proposing DAO, must be a proper ancestor
128 target *commondao.CommonDAO
129 toAdd, toRemove []address
130}
131
132func (ancestorCouncilUpdatePropDefinition) Title() string { return "Ancestor Council Update" }
133func (ancestorCouncilUpdatePropDefinition) VotingPeriod() time.Duration { return time.Hour * 24 * 7 }
134
135// isTrustedMarkdownBody marks Body as self-assembled markdown.
136func (ancestorCouncilUpdatePropDefinition) isTrustedMarkdownBody() {}
137
138// Threshold returns the tally threshold: ancestor council modification
139// requires a supermajority (:1531-1532).
140func (ancestorCouncilUpdatePropDefinition) Threshold() commondao.Threshold {
141 return commondao.ThresholdSupermajority
142}
143
144func (p ancestorCouncilUpdatePropDefinition) Body() string {
145 var b strings.Builder
146
147 b.WriteString(md.Paragraph(md.Bold("Target DAO:") + "\n" + daoMDLink(p.target)))
148
149 if len(p.toAdd) > 0 {
150 b.WriteString(md.Paragraph(
151 md.Bold("Council Members to Add:") + "\n" + md.BulletList(addrStrings(p.toAdd)),
152 ))
153 }
154
155 if len(p.toRemove) > 0 {
156 b.WriteString(md.Paragraph(
157 md.Bold("Council Members to Remove:") + "\n" + md.BulletList(addrStrings(p.toRemove)),
158 ))
159 }
160
161 return b.String()
162}
163
164func (p ancestorCouncilUpdatePropDefinition) Validate() error {
165 if err := assertIsProperAncestor(p.dao, p.target); err != nil {
166 return err
167 }
168
169 // Same overlap check as the self-mutating update: adds and removes are
170 // idempotent, so only a contradictory same-address add+remove is rejected.
171 for _, a := range p.toAdd {
172 for _, r := range p.toRemove {
173 if a == r {
174 return errors.New("address is added and removed at once: " + a.String())
175 }
176 }
177 }
178 return nil
179}
180
181func (p ancestorCouncilUpdatePropDefinition) Executor() commondao.ExecFunc {
182 return p.execute
183}
184
185func (p ancestorCouncilUpdatePropDefinition) execute(_ int, sub realm) error {
186 return p.target.UpdateCouncil(p.toAdd, p.toRemove)
187}
188
189// addrStrings converts addresses for markdown list rendering.
190func addrStrings(addrs []address) []string {
191 items := make([]string, 0, len(addrs))
192 for _, a := range addrs {
193 items = append(items, a.String())
194 }
195 return items
196}