Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}