Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}