1package dao23import (4 "testing"56 "gno.land/p/nt/uassert/v0"7 "gno.land/p/nt/urequire/v0"8)910// allowedDAOs is the sole authorization for UpdateImpl, memberstore.Get,11// treasury.Send and treasury.SetTokenKeys, and InAllowedDAOs() fails OPEN when12// it is empty — the bootstrap window that lets the genesis MsgRun seed the13// member set before lockdown.14//15// These tests pin that the transition empty -> non-empty is one-way: once the16// DAO is locked down, UpdateImpl cannot put it back into the fail-open state.17// Both paths below reopened the gate before the guard, because18// NewUpdateRequest copies nil into a NON-nil empty slice and the old test was19// `r.AllowedDAOs != nil`.20//21// These call the real UpdateImpl through a code realm rather than replaying22// its logic, so reverting the guard makes them fail.23func TestUpdateImplIgnoresEmptyAllowedDAOs(cur realm, t *testing.T) {24 savedDAOs, savedDAO := allowedDAOs, dao25 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()2627 lock := func() {28 allowedDAOs = nil // reopen so the next UpdateImpl is permitted29 testing.SetRealm(testing.NewCodeRealm(v0))30 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})31 uassert.False(t, InAllowedDAOs(invalid), "precondition: locked down")32 }3334 // Path 1: NewUpdateRequest(d, nil) — "swap the implementation, leave35 // permissions alone", and the form v0/loader uses.36 lock()37 testing.SetRealm(testing.NewCodeRealm(v0))38 UpdateImpl(cross(cur), NewUpdateRequest(&dummyDao{}, nil))39 uassert.False(t, InAllowedDAOs(invalid),40 "a nil AllowedDAOs must not reopen the permission gate")4142 // Path 2: an explicitly empty slice via the struct literal.43 lock()44 testing.SetRealm(testing.NewCodeRealm(v0))45 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{}})46 uassert.False(t, InAllowedDAOs(invalid),47 "an empty AllowedDAOs must not reopen the permission gate")4849 // A legitimate extension of the list must still apply.50 lock()51 testing.SetRealm(testing.NewCodeRealm(v0))52 UpdateImpl(cross(cur), NewUpdateRequest(&dummyDao{}, []string{v0, v1}))53 uassert.True(t, InAllowedDAOs(v1), "a non-empty AllowedDAOs must still be stored")54 uassert.False(t, InAllowedDAOs(invalid), "and must not admit anyone else")55}5657// The bootstrap window itself must survive the guard: with no allowlist58// configured yet, any caller is allowed so the genesis MsgRun can seed the59// member set and then lock down.60func TestBootstrapWindowStillOpen(t *testing.T) {61 saved := allowedDAOs62 defer func() { allowedDAOs = saved }()6364 allowedDAOs = nil65 uassert.True(t, InAllowedDAOs("gno.land/r/gov/dao/loader/v0"),66 "an unset allowlist must stay open for genesis bootstrap")67}6869// len(AllowedDAOs) != 0 is not sufficient on its own. InAllowedDAOs compares by70// exact string and a user realm's PkgPath() is "", so a single "" entry admits71// any caller whose previous frame is a user realm — the same fail-open outcome72// the guard exists to prevent. NewUpgradeDaoImplRequest passes its realmPkg73// argument straight into the list, so an empty one reaches here.74func TestUpdateImplRejectsBlankAllowedDAOEntry(cur realm, t *testing.T) {75 savedDAOs, savedDAO := allowedDAOs, dao76 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()7778 for _, blank := range []string{"", " "} {79 allowedDAOs = nil80 testing.SetRealm(testing.NewCodeRealm(v0))81 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})8283 testing.SetRealm(testing.NewCodeRealm(v0))84 urequire.AbortsWithMessage(t, cur,85 "AllowedDAOs entries must be realm paths; got an empty one",86 func() {87 UpdateImpl(cross(cur), UpdateRequest{88 DAO: &dummyDao{},89 AllowedDAOs: []string{v0, blank},90 })91 })9293 uassert.False(t, InAllowedDAOs(""),94 "a blank entry must never make it into the allowlist")95 uassert.True(t, InAllowedDAOs(v0),96 "the rejected request must leave the previous allowlist intact")97 }98}99100// A padded entry passes a plain non-blank test but is useless: entries are101// stored exactly as given and InAllowedDAOs compares whole strings, so102// " gno.land/r/x " matches no caller. The list is still non-empty, so the103// bootstrap window is closed. A proposal that padded every entry would lock104// the DAO out of its own allowlist with no way back.105func TestUpdateImplRejectsPaddedAllowedDAOEntry(cur realm, t *testing.T) {106 savedDAOs, savedDAO := allowedDAOs, dao107 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()108109 for _, padded := range []string{" " + v1, v1 + " ", "\t" + v1} {110 allowedDAOs = nil111 testing.SetRealm(testing.NewCodeRealm(v0))112 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})113114 testing.SetRealm(testing.NewCodeRealm(v0))115 urequire.AbortsWithMessage(t, cur,116 "AllowedDAOs entries must not have leading or trailing spaces; entry 1",117 func() {118 UpdateImpl(cross(cur), UpdateRequest{119 DAO: &dummyDao{},120 AllowedDAOs: []string{v0, padded},121 })122 })123124 uassert.False(t, InAllowedDAOs(padded),125 "a padded entry must never make it into the allowlist")126 uassert.True(t, InAllowedDAOs(v0),127 "the rejected request must leave the previous allowlist intact")128 }129}130AllowedDAOs() []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.