types.gno
7.68 Kb · 294 lines
1package dao
2
3import (
4 "errors"
5
6 "gno.land/p/nt/bptree/v0"
7 "gno.land/p/nt/seqid/v0"
8)
9
10type ProposalID int64
11
12func (pid ProposalID) String() string {
13 return seqid.ID(pid).String()
14}
15
16// VoteOption is the limited voting option for a DAO proposal
17// New govDAOs can create their own VoteOptions if needed in the
18// future.
19type VoteOption string
20
21const (
22 AbstainVote VoteOption = "ABSTAIN" // Side is not chosen
23 YesVote VoteOption = "YES" // Proposal should be accepted
24 NoVote VoteOption = "NO" // Proposal should be rejected
25)
26
27type VoteRequest struct {
28 Option VoteOption
29 ProposalID ProposalID
30 Metadata interface{}
31}
32
33func NewVoteRequest(option VoteOption, proposalID ProposalID) VoteRequest {
34 return VoteRequest{
35 Option: option,
36 ProposalID: proposalID,
37 }
38}
39
40func NewVoteRequestWithMetadata(option VoteOption, proposalID ProposalID, metadata interface{}) VoteRequest {
41 return VoteRequest{
42 Option: option,
43 ProposalID: proposalID,
44 Metadata: metadata,
45 }
46}
47
48func NewProposalRequest(title string, description string, executor Executor) ProposalRequest {
49 return ProposalRequest{
50 title: title,
51 description: description,
52 executor: executor,
53 }
54}
55
56func NewProposalRequestWithFilter(title string, description string, executor Executor, filter Filter) ProposalRequest {
57 return ProposalRequest{
58 title: title,
59 description: description,
60 executor: executor,
61 filter: filter,
62 }
63}
64
65type Filter interface{}
66
67type ProposalRequest struct {
68 title string
69 description string
70 executor Executor
71 filter Filter
72}
73
74func (p *ProposalRequest) Title() string {
75 return p.title
76}
77
78func (p *ProposalRequest) Description() string {
79 return p.description
80}
81
82func (p *ProposalRequest) Filter() Filter {
83 return p.filter
84}
85
86type Proposal struct {
87 author address
88
89 title string
90 description string
91
92 executor Executor
93 allowedDAOs []string
94}
95
96func (p *Proposal) Author() address {
97 return p.author
98}
99
100func (p *Proposal) Title() string {
101 return p.title
102}
103
104func (p *Proposal) Description() string {
105 return p.description
106}
107
108func (p *Proposal) ExecutorString() string {
109 if p.executor != nil {
110 return p.executor.String()
111 }
112
113 return ""
114}
115
116func (p *Proposal) ExecutorCreationRealm() string {
117 if p.executor != nil {
118 return p.executor.CreationRealm()
119 }
120
121 return ""
122}
123
124func (p *Proposal) AllowedDAOs() []string {
125 return append([]string(nil), p.allowedDAOs...)
126}
127
128type Proposals struct {
129 seq seqid.ID
130 *bptree.BPTree // *bptree.BPTree[ProposalID]*Proposal
131}
132
133func NewProposals() *Proposals {
134 return &Proposals{BPTree: bptree.NewBPTree32()}
135}
136
137func (ps *Proposals) SetProposal(p *Proposal) ProposalID {
138 pid := ProposalID(int64(ps.seq))
139 updated := ps.Set(pid.String(), p)
140 if updated {
141 panic("fatal error: Override proposals is not allowed")
142 }
143 ps.seq = ps.seq.Next()
144 return pid
145}
146
147func (ps *Proposals) GetProposal(pid ProposalID) *Proposal {
148 pv, ok := ps.Get(pid.String())
149 if !ok {
150 return nil
151 }
152
153 return pv.(*Proposal)
154}
155
156type Executor interface {
157 Execute(cur realm) error
158 String() string
159 CreationRealm() string
160}
161
162// NewSimpleExecutor constructs an Executor whose creationRealm is captured
163// from rlm.PkgPath() at construction time. The IsCurrent() check rejects
164// stale or stashed realm values so the captured value is the authentic
165// caller realm. creationRealm is display-only (rendered as "Executor
166// created in: ..." in proposal listings) — no auth gate downstream.
167func NewSimpleExecutor(_ int, rlm realm, callback func(realm) error, description string) *SimpleExecutor {
168 if !rlm.IsCurrent() {
169 panic("NewSimpleExecutor: rlm is not the caller's live cur (stale capture or sibling frame)")
170 }
171 if callback == nil {
172 panic("executor callback must not be nil")
173 }
174
175 return &SimpleExecutor{
176 callback: callback,
177 desc: description,
178 creationRealm: rlm.PkgPath(),
179 }
180}
181
182// SimpleExecutor implements the Executor interface using
183// a callback function and a description string.
184type SimpleExecutor struct {
185 callback func(realm) error
186 desc string
187 creationRealm string
188}
189
190func (e *SimpleExecutor) Execute(cur realm) error {
191 // Check if executor was created using the constructor func
192 if e.callback == nil {
193 return nil
194 }
195
196 return e.callback(cross(cur))
197}
198
199func (e *SimpleExecutor) String() string {
200 return e.desc
201}
202
203func (e *SimpleExecutor) CreationRealm() string {
204 return e.creationRealm
205}
206
207func NewSafeExecutor(e Executor) *SafeExecutor {
208 return &SafeExecutor{
209 e: e,
210 }
211}
212
213// SafeExecutor wraps an Executor to only allow its execution
214// by allowed govDAOs.
215type SafeExecutor struct {
216 e Executor
217}
218
219func (e *SafeExecutor) Execute(cur realm) error {
220 // Verify the caller is an adequate Realm
221 if !InAllowedDAOs(cur.Previous().PkgPath()) {
222 return errors.New("execution only allowed by validated govDAOs")
223 }
224
225 return e.e.Execute(cross(cur))
226}
227
228func (e *SafeExecutor) String() string {
229 return e.e.String()
230}
231
232func (e *SafeExecutor) CreationRealm() string {
233 return e.e.CreationRealm()
234}
235
236// DAO is the govDAO implementation interface. All mutating/auth-gated
237// methods take rlm as their realm-typed parameter in the second position
238// (the `_ int, rlm realm` non-crossing form): callers thread the proxy's
239// cur as data without forcing a realm transition, so the impl's existing
240// unsafe.CurrentRealm()-based auth gates (isValidCall, memberstore.Get)
241// continue to see the proxy realm. Render stays unchanged.
242type DAO interface {
243 // PreCreateProposal is called just before creating a new Proposal
244 // It is intended to be used to get the address of the proposal, that
245 // may vary depending on the DAO implementation, and to validate that
246 // the requester is allowed to do a proposal
247 PreCreateProposal(_ int, rlm realm, r ProposalRequest) (address, error)
248
249 // PostCreateProposal is called after creating the Proposal. It is
250 // intended to be used as a way to store a new proposal status, that
251 // depends on the actuall govDAO implementation
252 PostCreateProposal(_ int, rlm realm, r ProposalRequest, pid ProposalID)
253
254 // VoteOnProposal will send a petition to vote for a specific proposal
255 // to the actual govDAO implementation
256 VoteOnProposal(_ int, rlm realm, r VoteRequest) error
257
258 // PreExecuteProposal is called when someone is trying to execute a proposal by ID.
259 // Is intended to be used to validate who can trigger the proposal execution.
260 PreExecuteProposal(_ int, rlm realm, pid ProposalID) (bool, error)
261
262 // ExecuteProposal executes the proposal executor and on error changes proposal
263 // status to denied with the error message being the denial reason.
264 // It returns the executor error when it fails.
265 ExecuteProposal(_ int, rlm realm, pid ProposalID, e Executor) error
266
267 // Render will return a human-readable string in markdown format that
268 // will be used to show new data through the dao proxy entrypoint.
269 // Crossing: the chain query layer auto-injects .cur, and
270 // implementations forward cur to internal rlm-aware helpers (mux
271 // RenderRlm + downstream cross(rlm) reads).
272 Render(cur realm, pkgpath string, path string) string
273}
274
275type UpdateRequest struct {
276 DAO DAO
277 AllowedDAOs []string
278}
279
280// NewUpdateRequest copies allowedDAOs into a fresh slice owned by
281// /r/gov/dao. Under the storage=authority model, if we stored the
282// caller-passed slice directly, the base ArrayValue would retain
283// PkgID = caller_realm: storage rent would attribute to caller, and
284// /r/gov/dao could not mutate (e.g. append to) its own copy without
285// a DidUpdate panic. The internal copy ensures the UpdateRequest
286// and its AllowedDAOs both live entirely in /r/gov/dao's authority.
287func NewUpdateRequest(d DAO, allowedDAOs []string) UpdateRequest {
288 cp := make([]string, len(allowedDAOs))
289 copy(cp, allowedDAOs)
290 return UpdateRequest{
291 DAO: d,
292 AllowedDAOs: cp,
293 }
294}