Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}