Kind Realm (renderable)
Name v0
Namespace gov / dao / impl
Exported functions 17
Module gno.land/r/gov/dao/impl/v0
gno 0.9 render.gno gno
⧉
1 package impl 2 3 import ( 4 "chain/runtime" 5 "strconv" 6 "strings" 7 8 AddMember (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}, addr string)
GetInstance (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}) *gno.land/r/gov/dao/impl/v0.GovDAO
NewAddMemberRequest (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}, addr string, tier string, portfolio string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewChangeLawRequest (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}, newLaw struct{Supermajority float64}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewFilterByTier (tier string) struct{Tier string}
NewGovDAO () *gno.land/r/gov/dao/impl/v0.GovDAO
NewLaw (supermajority float64) struct{Supermajority float64}
NewPromoteMemberRequest (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}, addr string, fromTier string, toTier string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewProposalsStatuses () struct{BPTree *gno.land/p/nt/bptree/v0.BPTree}
NewRender (d *gno.land/r/gov/dao/impl/v0.GovDAO) *gno.land/r/gov/dao/impl/v0.render
NewTreasuryGRC20TokensUpdate (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}, newTokenKeys []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewTreasuryPaymentRequest (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}, payment interface {BankerID func() string; String func() string}, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewUpgradeDaoImplRequest (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}, newDao 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}, realmPkg string, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewWithdrawMemberRequest (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}, addr string, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
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}, in string) string
StringifyProposal (p *gno.land/r/gov/dao.Proposal) string
StringifyVotes (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}, ps *gno.land/r/gov/dao/impl/v0.proposalStatus) 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.
"gno.land/p/moul/helplink/v0"
9 "gno.land/p/moul/md/v0"
10 "gno.land/p/nt/bptree/pager/v0"
11 "gno.land/p/nt/markdown/sanitize/v0"
12 "gno.land/p/nt/mux/v0"
13 "gno.land/p/nt/seqid/v0"
14 "gno.land/p/nt/ufmt/v0"
15 "gno.land/r/gov/dao"
16 "gno.land/r/sys/users"
17 )
18
19 type render struct {
20 relativeRealmPath string
21 router *mux.Router
22 pssPager *pager.Pager
23 }
24
25 func NewRender(d *GovDAO) *render {
26 ren := &render{
27 pssPager: pager.NewPager(d.pss.BPTree, 5 , true ),
28 }
29
30 r := mux.NewRouter()
31
32 // Handlers use mux's rlm-aware shape: rlm is supplied at RenderRlm
33 // dispatch time rather than captured at NewRender time. This lets
34 // downstream crossing reads (dao.GetProposal etc.) use cross(rlm)
35 // without relying on bare cross or restructuring the router.
36 r.HandleFuncRlm( "" , func (_ int , rlm realm, rw *mux.ResponseWriter, req *mux.Request) {
37 rw.Write(ren.renderActiveProposals( 0 , rlm, req.RawPath, d))
38 })
39
40 r.HandleFuncRlm( "{pid}" , func (_ int , rlm realm, rw *mux.ResponseWriter, req *mux.Request) {
41 rw.Write(ren.renderProposalPage( 0 , rlm, req.GetVar( "pid" ), d))
42 })
43
44 r.HandleFuncRlm( "{pid}/votes" , func (_ int , rlm realm, rw *mux.ResponseWriter, req *mux.Request) {
45 rw.Write(ren.renderVotesForProposal( 0 , rlm, req.GetVar( "pid" ), d))
46 })
47
48 ren.router = r
49
50 return ren
51 }
52
53 func (ren *render) Render(_ int , rlm realm, pkgPath string , path string ) string {
54 relativePath, found := strings.CutPrefix(pkgPath, runtime.ChainDomain())
55 if !found {
56 panic(ufmt.Sprintf(
57 "realm package with unexpected name found: %v in chain domain %v" ,
58 pkgPath, runtime.ChainDomain()))
59 }
60 ren.relativeRealmPath = relativePath
61 return ren.router.RenderRlm( 0 , rlm, path)
62 }
63
64 func (ren *render) renderActiveProposals(_ int , rlm realm, url string , d *GovDAO) string {
65 out := "# GovDAO\n"
66 out += "## Members\n"
67 out += "[> Go to Memberstore <](/r/gov/dao/memberstore/v0)\n"
68 out += "## Proposals\n"
69 page, perr := ren.pssPager.GetPageByPath(url)
70 if perr != nil {
71 // A query url.Parse rejects (e.g. a control byte) is the caller's own
72 // malformed input; render the default first page instead of aborting
73 // this read-only render. ParseQuery already tolerates bad page/size
74 // values, so every currently-working input is unaffected.
75 page = ren.pssPager.GetPage( 1 )
76 }
77 if len(page.Items) == 0 {
78 out += "\nNo proposals yet.\n\n"
79 return out
80 }
81
82 for _, item := range page.Items {
83 seqpid, err := seqid.FromString(item.Key)
84 if err != nil {
85 continue
86 }
87 out += ren.renderProposalListItem( 0 , rlm, ufmt.Sprintf( "%v" , int64 (seqpid)), d)
88 out += "---\n\n"
89 }
90
91 out += page.Picker( "" )
92
93 return out
94 }
95
96 func (ren *render) renderProposalPage(_ int , rlm realm, sPid string , d *GovDAO) string {
97 pid, err := strconv.ParseInt(sPid, 10 , 64 )
98 if err != nil {
99 // err echoes the caller's raw pid segment (strconv quotes it but leaves
100 // markdown/HTML metachars); escape+clamp it like every other untrusted
101 // slot on this page, since Render is reachable unauthenticated.
102 return ufmt.Sprintf( "# Error: Invalid proposal ID format.\n\n\n%s\n\n" , md.EscapeText(clampField(err.Error(), maxRenderedError)))
103 }
104
105 p, err := dao.GetProposal(dao.ProposalID(pid))
106 if err != nil {
107 return ufmt.Sprintf( "# Proposal not found\n\n%s" , err.Error())
108 }
109
110 // pid is user-supplied and the proposal itself lives on the proxy, so
111 // GetProposal can succeed for a proposal this implementation has no status
112 // for — the same post-upgrade case PreExecuteProposal guards (statuses live
113 // on the instance, so a fresh instance has none). Without this the page
114 // panics at ps.String below. renderProposalListItem needs no such guard:
115 // its pids are iterated out of pss itself, so a status always exists.
116 ps := d.pss.GetStatus(dao.ProposalID(pid))
117 if ps == nil {
118 return ufmt.Sprintf( "# Proposal #%v not available\n\nThis proposal was not created by the current govDAO implementation, so it has no voting status here." , pid)
119 }
120
121 out := ufmt.Sprintf( "## Prop #%v - %v\n" , pid, md.EscapeText(clampField(p.Title(), maxRenderedTitle)))
122 out += "Author: " + tryResolveAddr(p.Author()) + "\n\n"
123
124 out += p.Description()
125 out += "\n\n"
126
127 // Add executor metadata if available
128 if p.ExecutorString() != "" {
129 out += ufmt.Sprintf(`This proposal contains the following metadata:
130
131 %s
132
133 `, p.ExecutorString())
134 }
135
136 // Disclosed independently of the description: this names the realm whose
137 // code runs if the proposal passes. Sharing the gate above hid it for every
138 // proposal built with an empty executor description — 16 call sites across
139 // 7 production realms, nearly all r/sys/* governance actions.
140 //
141 // Escaped, not trusted: CreationRealm() is dispatched through the public
142 // dao.Executor interface, so only SimpleExecutor's value is VM-captured;
143 // a third-party executor returns any string it likes.
144 // Guarded on the sanitized value, not the raw one: InlineCode returns ""
145 // for input that strips to nothing (bidi/zero-width only), and TrimSpace
146 // catches plain whitespace, so neither prints a label with nothing after
147 // it. This is tidiness, not a boundary: a zero-width/space mixture still
148 // renders an empty-looking span, and a hostile executor can always return
149 // a plausible-looking lie. The security property is the escaping below.
150 if cr := sanitize.InlineCode(strings.TrimSpace(clampField(p.ExecutorCreationRealm(), maxRenderedRealm))); cr != "" {
151 out += ufmt.Sprintf( "Executor created in: %s\n" , cr)
152 out += "\n\n"
153 }
154
155 out += "\n\n---\n\n"
156 out += ps.String( 0 , rlm)
157 out += "\n"
158 out += ufmt.Sprintf( "[Detailed voting list](%v:%v/votes)" , ren.relativeRealmPath, pid)
159 out += "\n\n---\n\n"
160
161 out += renderActionBar(ufmt.Sprintf( "%v" , pid))
162
163 return out
164 }
165
166 func (ren *render) renderProposalListItem(_ int , rlm realm, sPid string , d *GovDAO) string {
167 pid, err := strconv.ParseInt(sPid, 10 , 64 )
168 if err != nil {
169 // err echoes the caller's raw pid segment (strconv quotes it but leaves
170 // markdown/HTML metachars); escape+clamp it like every other untrusted
171 // slot on this page, since Render is reachable unauthenticated.
172 return ufmt.Sprintf( "# Error: Invalid proposal ID format.\n\n\n%s\n\n" , md.EscapeText(clampField(err.Error(), maxRenderedError)))
173 }
174
175 p, err := dao.GetProposal(dao.ProposalID(pid))
176 if err != nil {
177 return ufmt.Sprintf( "# Proposal not found\n\n%s\n\n" , err.Error())
178 }
179
180 ps := d.pss.GetStatus(dao.ProposalID(pid))
181 out := ufmt.Sprintf( "### [Prop #%v - %v](%v:%v)\n" , pid, md.EscapeText(clampField(p.Title(), maxRenderedTitle)), ren.relativeRealmPath, pid)
182 out += ufmt.Sprintf( "Author: %s\n\n" , tryResolveAddr(p.Author()))
183
184 out += "Status: " + getPropStatus(ps)
185 out += "\n\n"
186
187 out += "Tiers eligible to vote: "
188 out += strings.Join(ps.TiersAllowedToVote, ", " )
189
190 out += "\n\n"
191 return out
192 }
193
194 func (ren *render) renderVotesForProposal(_ int , rlm realm, sPid string , d *GovDAO) string {
195 pid, err := strconv.ParseInt(sPid, 10 , 64 )
196 if err != nil {
197 // err echoes the caller's raw pid segment (strconv quotes it but leaves
198 // markdown/HTML metachars); escape+clamp it like every other untrusted
199 // slot on this page, since Render is reachable unauthenticated.
200 return ufmt.Sprintf( "# Error: Invalid proposal ID format.\n\n\n%s\n\n" , md.EscapeText(clampField(err.Error(), maxRenderedError)))
201 }
202
203 ps := d.pss.GetStatus(dao.ProposalID(pid))
204 if ps == nil {
205 return ufmt.Sprintf( "# Proposal not found\n\nProposal %v does not exist." , pid)
206 }
207
208 out := ""
209 out += ufmt.Sprintf( "# Proposal #%v - Vote List\n\n" , pid)
210 out += StringifyVotes( 0 , rlm, ps)
211
212 return out
213 }
214
215 func isPropActive(ps *proposalStatus) bool {
216 return !ps.Accepted && !ps.Denied
217 }
218
219 func getPropStatus(ps *proposalStatus) string {
220 if ps == nil {
221 return "UNKNOWN"
222 }
223 if ps.Accepted {
224 return "ACCEPTED"
225 } else if ps.Denied {
226 return "REJECTED"
227 }
228 return "ACTIVE"
229 }
230
231 func renderActionBar(sPid string ) string {
232 out := "### Actions\n"
233
234 proxy := helplink.Realm( "gno.land/r/gov/dao" )
235 out += proxy.Func( "Vote YES" , "MustVoteOnProposalSimple" , "pid" , sPid, "option" , "YES" ) + " | "
236 out += proxy.Func( "Vote NO" , "MustVoteOnProposalSimple" , "pid" , sPid, "option" , "NO" ) + " | "
237 out += proxy.Func( "Vote ABSTAIN" , "MustVoteOnProposalSimple" , "pid" , sPid, "option" , "ABSTAIN" )
238
239 out += "\n\n"
240 out += "WARNING: Please double check transaction data before voting."
241 return out
242 }
243
244 // tryResolveAddr renders the author/voter as a username link. RenderLink
245 // interpolates the username raw into "[@name](/u/name)" (r/sys/users), so this
246 // is markdown-safe ONLY because r/sys/users validateName restricts names to
247 // ^[a-z][a-z0-9]*([_-][a-z0-9]+)*$ (max 64) — no markdown/HTML metachar. If that
248 // charset ever loosens, this line and writeVotes in types.gno need escaping.
249 func tryResolveAddr(addr address) string {
250 userData := users.ResolveAddress(addr)
251 if userData == nil {
252 return addr.String()
253 }
254 return userData.RenderLink( "" )
255 }
256