commondao.gno
4.23 Kb · 135 lines
1package commondao
2
3import (
4 "chain"
5 "strconv"
6
7 "gno.land/p/moul/txlink/v0"
8 "gno.land/p/nt/bptree/v0"
9 "gno.land/p/nt/commondao/v0"
10 "gno.land/p/nt/seqid/v0"
11)
12
13// pkgPath is this realm's package path, fixed regardless of quarantine
14// location. DAO treasury addresses derive from it, so they are stable
15// across a SAME-PATH redeploy. Moving to a different package path (e.g.
16// .../v1) changes cur.Sub derivation and would strand existing treasuries
17// unless a drain executor ran first (see gnovm/adr/pr5890_realm_sub.md
18// §Cross-host identity); this realm ships no such migration executor.
19const pkgPath = "gno.land/r/nt/commondao/v0"
20
21// CommonDAOID is the ID of the realm's DAO.
22const CommonDAOID uint64 = 1
23
24var (
25 daoID seqid.ID
26 realmLink = txlink.Realm(pkgPath)
27 daos = bptree.NewBPTree32() // string(ID) -> *commondao.CommonDAO
28 invites = bptree.NewBPTree32() // string(address) -> address(inviter)
29 creators = bptree.NewBPTree32() // string(address) -> struct{}; consumed-invite marker
30 listed = bptree.NewBPTree32() // string(ID) -> uint64(ID); home-index opt-in
31)
32
33func getDAO(daoID uint64) *commondao.CommonDAO {
34 key := makeIDKey(daoID)
35 if v := daos.Get(key); v != nil {
36 return v.(*commondao.CommonDAO)
37 }
38 return nil
39}
40
41func mustGetDAO(daoID uint64) *commondao.CommonDAO {
42 dao := getDAO(daoID)
43 if dao == nil {
44 panic("DAO not found")
45 }
46 return dao
47}
48
49func makeIDKey(daoID uint64) string {
50 return seqid.ID(daoID).String()
51}
52
53// isCreator reports whether an address has already consumed an invite and
54// may create further DAOs without another one.
55func isCreator(addr address) bool {
56 return creators.Has(addr.String())
57}
58
59// isListed reports whether a DAO opted into the realm's public home index.
60func isListed(daoID uint64) bool {
61 return listed.Has(makeIDKey(daoID))
62}
63
64// setListed adds or removes a DAO from the realm's public home index.
65func setListed(daoID uint64, v bool) {
66 key := makeIDKey(daoID)
67 if v {
68 // Store the ID as the value so renderHome can paginate over the
69 // listed set directly (keys are cford32-encoded, order-preserving).
70 listed.Set(key, daoID)
71 } else {
72 listed.Remove(key)
73 }
74}
75
76// subpathOf returns the realm sub-identity subpath that acts for a DAO.
77func subpathOf(daoID uint64) string {
78 return "dao/" + strconv.FormatUint(daoID, 10)
79}
80
81// daoAddress derives a DAO's treasury address: the address of the realm
82// sub-identity cur.Sub(subpathOf(daoID)) that executors mint to move its
83// funds. Pure derivation; DAO IDs are never reused, so the address is
84// collision free and stable.
85func daoAddress(daoID uint64) address {
86 return chain.DerivePkgSubAddr(pkgPath, subpathOf(daoID))
87}
88
89func createDAO(name, purpose, description string, members ...address) *commondao.CommonDAO {
90 if len(members) == 0 {
91 panic("a DAO requires at least one initial council member")
92 }
93
94 id := daoID.Next()
95 dao := commondao.New(newDAOOptions(uint64(id), name, purpose, description, nil, members)...)
96 daos.Set(id.String(), dao)
97 return dao
98}
99
100func createSubDAO(parent *commondao.CommonDAO, name, purpose, description string, members ...address) *commondao.CommonDAO {
101 if len(members) == 0 {
102 panic("a SubDAO requires at least one initial council member")
103 }
104
105 // WithParent also registers the new DAO as one of parent's children
106 id := daoID.Next()
107 dao := commondao.New(newDAOOptions(uint64(id), name, purpose, description, parent, members)...)
108 daos.Set(id.String(), dao)
109 return dao
110}
111
112func newDAOOptions(id uint64, name, purpose, description string, parent *commondao.CommonDAO, members []address) []commondao.Option {
113 opts := []commondao.Option{
114 commondao.WithID(id),
115 commondao.WithName(name),
116 commondao.WithPurpose(purpose),
117 commondao.WithDescription(description),
118 commondao.WithAddress(daoAddress(id)),
119 }
120 if parent != nil {
121 opts = append(opts, commondao.WithParent(parent))
122 }
123 for _, m := range members {
124 opts = append(opts, commondao.WithCouncilMember(m))
125 }
126
127 // Seed the default proposal kinds: every new DAO (genesis, user-created
128 // or sub-DAO) starts with these registered. The opt-in catalog kind
129 // (execution) is deliberately NOT seeded — a DAO gains it only after a
130 // supermajority CreateRegisterKindProposal.
131 for _, k := range defaultProposalKinds {
132 opts = append(opts, commondao.WithProposalKind(k))
133 }
134 return opts
135}