1package dao23import (4 "chain"5 "errors"6 "strconv"7 "strings"89 "gno.land/p/nt/ufmt/v0"10)1112// dao is the actual govDAO implementation, having all the needed business logic13var dao DAO1415// allowedDAOs contains realms that can be used to update the actual govDAO implementation,16// and validate Proposals.17// This is like that to be able to rollback using a previous govDAO implementation in case18// the latest implementation has a breaking bug. After a test period, a proposal can be19// executed to remove all previous govDAOs implementations and leave the last one.20var allowedDAOs []string2122// proposals contains all the proposals in history.23var proposals *Proposals = NewProposals()2425// Render calls directly to Render's DAO implementation.26// This allows to have this realm as the main entry point for everything.27func Render(cur realm, p string) string {28 if dao == nil {29 return "DAO not initialized"30 }31 return dao.Render(cross(cur), cur.PkgPath(), p)32}3334// MustCreateProposal is an utility method that does the same as CreateProposal,35// but instead of erroing if something happens, it panics.36func MustCreateProposal(cur realm, r ProposalRequest) ProposalID {37 pid, err := CreateProposal(cur, r)38 if err != nil {39 panic(err.Error())40 }4142 return pid43}4445// ExecuteProposal will try to execute the proposal with the provided ProposalID.46// If the proposal was denied, it will return false. If the proposal is correctly47// executed, it will return true. If something happens this function will panic.48func ExecuteProposal(cur realm, pid ProposalID) bool {49 return executeProposal(cur, pid, false)50}5152// ExecuteOrRejectProposal executes the proposal with the provided ProposalID or rejects53// it when there is an execution error.54// If the proposal was denied, it will return false. If the proposal is correctly55// executed, it will return true, unless execution fails with an error, in which case56// proposal is rejected with the error as the reason.57// This function allows to finish proposals by rejecting them when there is a state58// change or an error in the proposal parameters that makes execution fail, potentially59// leaving the proposal active forever because it can't be successfully executed.60func ExecuteOrRejectProposal(cur realm, pid ProposalID) bool {61 return executeProposal(cur, pid, true)62}6364// CreateProposal will try to create a new proposal, that will be validated by the actual65// govDAO implementation. If the proposal cannot be created, an error will be returned.66func CreateProposal(cur realm, r ProposalRequest) (ProposalID, error) {67 if dao == nil {68 return -1, errors.New("DAO not initialized")69 }70 author, err := dao.PreCreateProposal(0, cur, r)71 if err != nil {72 return -1, err73 }7475 p := &Proposal{76 author: author,77 title: r.title,78 description: r.description,79 executor: r.executor,80 allowedDAOs: allowedDAOs[:],81 }8283 pid := proposals.SetProposal(p)84 dao.PostCreateProposal(0, cur, r, pid)8586 chain.Emit("ProposalCreated",87 "id", strconv.FormatInt(int64(pid), 10),88 )8990 return pid, nil91}9293func MustVoteOnProposal(cur realm, r VoteRequest) {94 if err := VoteOnProposal(cur, r); err != nil {95 panic(err.Error())96 }97}9899// VoteOnProposal sends a vote to the actual govDAO implementation.100// If the voter cannot vote the specified proposal, this method will return an error101// with the explanation of why.102func VoteOnProposal(cur realm, r VoteRequest) error {103 if dao == nil {104 return errors.New("DAO not initialized")105 }106 return dao.VoteOnProposal(0, cur, r)107}108109// MustVoteOnProposalSimple is like MustVoteOnProposal but intended to be used through gnokey with basic types.110func MustVoteOnProposalSimple(cur realm, pid int64, option string) {111 MustVoteOnProposal(cur, VoteRequest{112 Option: VoteOption(option),113 ProposalID: ProposalID(pid),114 })115}116117func MustGetProposal(pid ProposalID) *Proposal {118 p, err := GetProposal(pid)119 if err != nil {120 panic(err.Error())121 }122123 return p124}125126// GetProposal gets created proposal by its ID. Non-crossing pure read:127// looks up the proposal in this realm's package var. Callable directly128// from any realm without cross-call syntax.129func GetProposal(pid ProposalID) (*Proposal, error) {130 if dao == nil {131 return nil, errors.New("DAO not initialized")132 }133 prop := proposals.GetProposal(pid)134 if prop == nil {135 return nil, errors.New(ufmt.Sprintf("Proposal %v does not exist.", int64(pid)))136 }137 return prop, nil138}139140// UpdateImpl is a method intended to be used on a proposal.141// This method will update the current govDAO implementation142// to a new one. AllowedDAOs are a list of realms that can143// call this method, in case the new DAO implementation had144// a breaking bug. A nil DAO is ignored.145// If AllowedDAOs field is not set correctly, the actual DAO146// implementation wont be able to execute new Proposals!147//148// An empty AllowedDAOs is ignored rather than stored. An empty list makes149// InAllowedDAOs() return true for every caller — the bootstrap-only state that150// lets the genesis MsgRun seed the member set. Since this is the only site that151// assigns allowedDAOs, ignoring empty here makes the transition152// empty -> non-empty one-way: once locked down the DAO cannot be reopened,153// whether the empty value arrives as a literal []string{} or from154// NewUpdateRequest(d, nil), which copies nil into a non-nil empty slice.155// Individual entries must be non-blank realm paths; an empty entry would match156// a user realm's empty PkgPath() and is rejected.157func UpdateImpl(cur realm, r UpdateRequest) {158 // AGENTS.md: in a crossing function, always check IsCurrent() before159 // deriving caller identity from cur.Previous(). Redundant under the160 // crossing-frame guarantee, but mandated, and this is the single most161 // powerful entrypoint (it rewrites the allowlist and swaps the impl).162 if !cur.IsCurrent() {163 panic("UpdateImpl: realm value is not the caller's live cur")164 }165 gRealm := cur.Previous().PkgPath()166167 if !InAllowedDAOs(gRealm) {168 panic("permission denied for prev realm: " + gRealm)169 }170171 if len(r.AllowedDAOs) != 0 {172 // Every entry must be a real realm path. len() != 0 alone is the wrong173 // invariant: InAllowedDAOs compares by exact string, and a user realm's174 // PkgPath() is "", so a single "" entry admits any caller whose previous175 // frame is a user realm -- the same fail-open outcome this guard exists176 // to prevent, just spelled differently. An empty entry can only be a177 // drafting mistake, so reject the whole request rather than silently178 // dropping it and storing a list the proposal did not describe.179 for i, d := range r.AllowedDAOs {180 trimmed := strings.TrimSpace(d)181 if trimmed == "" {182 panic("AllowedDAOs entries must be realm paths; got an empty one")183 }184 // Entries are stored exactly as given, and InAllowedDAOs compares185 // whole strings, so an entry with surrounding spaces matches no186 // caller at all. A non-empty list also closes the bootstrap187 // window, so a list of only padded entries is locked shut against188 // everyone, the DAO included, with no way to reopen it.189 //190 // This does not make the list typo-proof, and is not meant to be:191 // any wrong path locks the DAO out exactly the same way, and no192 // check here can tell a typo from a realm that does not exist yet.193 // Whitespace is worth rejecting because it is the one spelling a194 // human reviewing the proposal cannot see. Rejected rather than195 // trimmed, so what gets stored is what the proposal said.196 // Reported by position, not by value. A panic message becomes the197 // proposal's DeniedReason, which is stored, and the entry is198 // caller-supplied and unbounded — echoing it would put an199 // arbitrary amount of someone else's text into this realm's200 // storage. The index is enough to find it in a list the proposal201 // author wrote.202 if d != trimmed {203 panic("AllowedDAOs entries must not have leading or trailing spaces; entry " + strconv.Itoa(i))204 }205 }206 // Stored as given. A defensive copy here looks prudent but would207 // guard a write no outside realm can perform, which was checked from208 // a separate realm rather than assumed:209 //210 // - Building this request as a literal fails outright, with211 // "cannot allocate gno.land/r/gov/dao.UpdateRequest in realm ...".212 // - Writing through a request obtained from NewUpdateRequest fails213 // with "cannot directly modify readonly tainted object".214 //215 // So the only way in is NewUpdateRequest, which copies already. Both216 // checks are language guarantees, not conventions.217 allowedDAOs = r.AllowedDAOs218 }219220 if r.DAO != nil {221 dao = r.DAO222 }223}224225func AllowedDAOs() []string {226 dup := make([]string, len(allowedDAOs))227 copy(dup, allowedDAOs)228 return dup229}230231func InAllowedDAOs(pkg string) bool {232 if len(allowedDAOs) == 0 {233 return true // corner case for initialization234 }235 for _, d := range allowedDAOs {236 if pkg == d {237 return true238 }239 }240 return false241}242243func executeProposal(cur realm, pid ProposalID, execErrorRejects bool) bool {244 if dao == nil {245 return false246 }247 execute, err := dao.PreExecuteProposal(0, cur, pid)248 if err != nil {249 panic(err.Error())250 }251252 if !execute {253 return false254 }255 prop, err := GetProposal(pid)256 if err != nil {257 panic(err.Error())258 }259260 err = dao.ExecuteProposal(0, cur, pid, prop.executor)261 if err != nil {262 if execErrorRejects {263 return false264 }265266 panic(err.Error())267 }268 return true269}270AllowedDAOs() []string
CreateProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, r struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}) (int64, interface {Error func() string})
ExecuteOrRejectProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pid int64) bool
ExecuteProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pid int64) bool
GetProposal(pid int64) (*gno.land/r/gov/dao.Proposal, interface {Error func() string})
InAllowedDAOs(pkg string) bool
MustCreateProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, r struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}) int64
MustGetProposal(pid int64) *gno.land/r/gov/dao.Proposal
MustVoteOnProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, r struct{Option gno.land/r/gov/dao.VoteOption; ProposalID gno.land/r/gov/dao.ProposalID; Metadata interface {}})
MustVoteOnProposalSimple(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pid int64, option string)
NewProposalRequest(title string, description string, executor interface {CreationRealm func() string; Execute func(.uverse.realm) .uverse.error; String func() string}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewProposalRequestWithFilter(title string, description string, executor interface {CreationRealm func() string; Execute func(.uverse.realm) .uverse.error; String func() string}, filter interface {}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewProposals() *gno.land/r/gov/dao.Proposals
NewSafeExecutor(e interface {CreationRealm func() string; Execute func(.uverse.realm) .uverse.error; String func() string}) *gno.land/r/gov/dao.SafeExecutor
NewSimpleExecutor(int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, callback func(.uverse.realm) .uverse.error, description string) *gno.land/r/gov/dao.SimpleExecutor
NewUpdateRequest(d interface {ExecuteProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalID, gno.land/r/gov/dao.Executor) .uverse.error; PostCreateProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalRequest, gno.land/r/gov/dao.ProposalID); PreCreateProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalRequest) (.uverse.address, .uverse.error); PreExecuteProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalID) (bool, .uverse.error); Render func(.uverse.realm, string, string) string; VoteOnProposal func(int, .uverse.realm, gno.land/r/gov/dao.VoteRequest) .uverse.error}, allowedDAOs []string) struct{DAO gno.land/r/gov/dao.DAO; AllowedDAOs []string}
NewVoteRequest(option string, proposalID int64) struct{Option gno.land/r/gov/dao.VoteOption; ProposalID gno.land/r/gov/dao.ProposalID; Metadata interface {}}
NewVoteRequestWithMetadata(option string, proposalID int64, metadata interface {}) struct{Option gno.land/r/gov/dao.VoteOption; ProposalID gno.land/r/gov/dao.ProposalID; Metadata interface {}}
Render(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, p string) string
UpdateImpl(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, r struct{DAO gno.land/r/gov/dao.DAO; AllowedDAOs []string})
VoteOnProposal(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, r struct{Option gno.land/r/gov/dao.VoteOption; ProposalID gno.land/r/gov/dao.ProposalID; Metadata interface {}}) interface {Error func() string}
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.