public.gno
4.84 Kb · 205 lines
1package commondao
2
3import (
4 "chain/runtime"
5 "strings"
6
7 "gno.land/p/nt/commondao/v0"
8)
9
10// TODO: Limit the number of DAOs per address, maybe discuss fees ranges to avoid spaming
11
12// Invite invites a user to the realm.
13// A user invitation is required to start creating new DAOs.
14func Invite(_ realm, invitee address) {
15 if !invitee.IsValid() {
16 panic("invalid address")
17 }
18
19 dao := mustGetDAO(CommonDAOID)
20 caller := runtime.PreviousRealm().Address()
21 if !dao.Members().Has(caller) {
22 panic("unauthorized")
23 }
24
25 invites.Set(invitee.String(), caller.String())
26}
27
28// IsInvited check if an address has an invitation to the realm.
29func IsInvited(addr address) bool {
30 return isInvited(addr)
31}
32
33// New creates a new CommonDAO.
34// An invitation is required to be able to create new DAOs.
35func New(_ realm, name string, options ...Option) *commondao.CommonDAO {
36 name = strings.TrimSpace(name)
37 assertDAONameIsValid(name)
38
39 caller := runtime.PreviousRealm().Address()
40 if !hasOwnership(caller) {
41 orig := runtime.OriginCaller()
42 assertIsInvited(orig)
43 invites.Remove(orig.String())
44 }
45
46 return createDAO(name, caller, options)
47}
48
49// NewSubDAO creates a new SubDAO.
50func NewSubDAO(_ realm, name string, parentID uint64, options ...Option) *commondao.CommonDAO {
51 caller := runtime.PreviousRealm().Address()
52 assertIsOwner(caller, parentID)
53
54 parentOptions := getOptions(parentID)
55 if !parentOptions.AllowChildren {
56 panic("SubDAO support is not enabled")
57 }
58
59 name = strings.TrimSpace(name)
60 assertDAONameIsValid(name)
61
62 parent := getDAO(parentID)
63 subDAO := createSubDAO(parent, name, options)
64 parent.Children().Append(subDAO)
65 return subDAO
66}
67
68// GetOptions returns the configuration of a DAO.
69// It panics if caller doesn't have ownership of the DAO instance.
70func GetOptions(daoID uint64) *Options {
71 if !isOwner(runtime.CurrentRealm().Address(), daoID) {
72 panic("unauthorized")
73 }
74
75 return getOptions(daoID)
76}
77
78// IsOwner checks if an address has ownership of a DAO.
79func IsOwner(addr address, daoID uint64) bool {
80 return isOwner(addr, daoID)
81}
82
83// TransferOwnership transfers DAO ownership to a different address.
84func TransferOwnership(cur realm, daoID uint64, newOwner address) {
85 assertIsOwner(runtime.PreviousRealm().Address(), daoID)
86
87 if !newOwner.IsValid() {
88 panic("invalid owner address")
89 }
90
91 caller := runtime.PreviousRealm().Address()
92 ids := getOwnership(caller)
93 for i, id := range ids {
94 if id == daoID {
95 ownership.Set(caller.String(), append(ids[:i], ids[i+1:]...))
96 ownership.Set(newOwner.String(), append(getOwnership(newOwner), id))
97
98 break
99 }
100 }
101}
102
103// Get returns a common DAO searched by ID.
104// It panics if caller doesn't have ownership of the DAO instance.
105// Only toplevel DAOs are returned, to get SubDAOs use `GetSubDAO()`.
106func Get(daoID uint64) *commondao.CommonDAO {
107 assertIsOwner(runtime.CurrentRealm().Address(), daoID)
108 return mustGetDAO(daoID)
109}
110
111// GetSize returns the number of SubDAOs under a specified root DAO.
112func GetSize(rootID uint64) int {
113 assertIsOwner(runtime.CurrentRealm().Address(), rootID)
114
115 tree := getTree(rootID)
116 if tree == nil {
117 return 0
118 }
119 return tree.Size()
120}
121
122// Vote submits a vote for a DAO proposal.
123func Vote(_ realm, daoID, proposalID uint64, vote commondao.VoteChoice, reason string) {
124 dao := mustGetDAO(daoID)
125 o := getOptions(dao.ID())
126 if !o.AllowVoting {
127 panic("voting is not enabled")
128 }
129
130 caller := runtime.PreviousRealm().Address()
131 assertCallerIsMember(caller, dao)
132
133 err := dao.Vote(caller, proposalID, vote, reason)
134 if err != nil {
135 panic(err)
136 }
137}
138
139// Execute executes a DAO proposal.
140func Execute(_ realm, daoID, proposalID uint64) {
141 dao := mustGetDAO(daoID)
142 o := getOptions(dao.ID())
143 if !o.AllowExecution {
144 panic("proposal execution is not enabled")
145 }
146
147 caller := runtime.PreviousRealm().Address()
148 assertCallerIsMember(caller, dao)
149
150 err := dao.Execute(proposalID)
151 if err != nil {
152 panic(err)
153 }
154}
155
156func hasOwnership(addr address) bool {
157 _, found := ownership.Get(addr.String())
158 return found
159}
160
161func isInvited(addr address) bool {
162 _, found := invites.Get(addr.String())
163 return found
164}
165
166func isOwner(addr address, daoID uint64) bool {
167 // Make sure to check the root DAO ID, in case daoID belongs to a SubDAO.
168 // This is required because ownership is assigned to the root DAO.
169 daoID = mustGetDAO(daoID).TopParent().ID()
170
171 for _, id := range getOwnership(addr) {
172 if id == daoID {
173 return true
174 }
175 }
176 return false
177}
178
179func assertIsInvited(addr address) {
180 if !isInvited(addr) {
181 panic("unauthorized")
182 }
183}
184
185func assertIsOwner(addr address, daoID uint64) {
186 if !isOwner(addr, daoID) {
187 panic("unauthorized")
188 }
189}
190
191func assertDAONameIsValid(name string) {
192 if name == "" {
193 panic("DAO name is empty")
194 }
195
196 if len(name) > 60 {
197 panic("DAO name is too long, max length is 60 characters")
198 }
199}
200
201func assertCallerIsMember(caller address, dao *commondao.CommonDAO) {
202 if !dao.Members().Has(caller) {
203 panic("caller is not a DAO member")
204 }
205}