Kind Realm (renderable)
Name v0
Namespace gov / dao / impl
Exported functions 17
Module gno.land/r/gov/dao/impl/v0
gno 0.9 executor_disclosure_filetest.gno gno
⧉
1 // PKGPATH: gno.land/r/test/disclosure 2 package disclosure 3 4 // Covers the executor-disclosure changes in an isolated realm. The unit tests 5 // in the impl package share proposal ids across files (govdao_test.gno asserts 6 // a hard-coded id) and swap the DAO implementation partway through, so these 7 // live here instead. 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.
8
9 import (
10 "strings"
11 "testing"
12
13 "gno.land/r/gov/dao"
14 "gno.land/r/gov/dao/impl/v0"
15 "gno.land/r/gov/dao/memberstore/v0"
16 )
17
18 const user address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"
19
20 func init(cur realm) {
21 memberstore.Get( 0 , cur).DeleteAll()
22 memberstore.Get( 0 , cur).SetTier(memberstore.T1)
23 memberstore.Get( 0 , cur).SetMember(memberstore.T1, user, memberstore.NewMember( 3 ))
24 dao.UpdateImpl(cross(cur), dao.NewUpdateRequest(impl.NewGovDAO(), nil ))
25 }
26
27 // hostileExecutor implements dao.Executor directly rather than through
28 // NewSimpleExecutor, which is what lets it choose its own CreationRealm.
29 // CreationRealm() is dispatched through the public dao.Executor interface, so
30 // only SimpleExecutor's value is VM-supplied from rlm.PkgPath().
31 type hostileExecutor struct {}
32
33 func (e *hostileExecutor) Execute(cur realm) error { return nil }
34
35 func (e *hostileExecutor) String() string { return "" }
36
37 func (e *hostileExecutor) CreationRealm() string {
38 return "gno.land/r/sys/params\n\n### Stats\n\n- **PROPOSAL HAS BEEN ACCEPTED**\n- YES PERCENT: 100%\n\n---\n"
39 }
40
41 // blankExecutor's CreationRealm is non-empty but strips to nothing:
42 // sanitize.InlineCode removes bidi and zero-width characters, so it returns "".
43 type blankExecutor struct {}
44
45 func (e *blankExecutor) Execute(cur realm) error { return nil }
46
47 func (e *blankExecutor) String() string { return "" }
48
49 func (e *blankExecutor) CreationRealm() string { return "\u200b\u200b\u202e" }
50
51 // whitespaceExecutor covers the other half of the guard: plain whitespace,
52 // which InlineCode would otherwise wrap in a padded, empty-looking span.
53 type whitespaceExecutor struct {}
54
55 func (e *whitespaceExecutor) Execute(cur realm) error { return nil }
56
57 func (e *whitespaceExecutor) String() string { return "" }
58
59 func (e *whitespaceExecutor) CreationRealm() string { return " \t " }
60
61 // hugeExecutor computes a very large CreationRealm while storing nothing.
62 // Sanitizing costs ~11,310 gas/byte and render is reachable unauthenticated
63 // under a 3,000,000,000 gas cap, so this must be clamped before escaping.
64 type hugeExecutor struct {}
65
66 func (e *hugeExecutor) Execute(cur realm) error { return nil }
67
68 func (e *hugeExecutor) String() string { return "" }
69
70 func (e *hugeExecutor) CreationRealm() string { return strings.Repeat( "z" , 20000 ) }
71
72 // fenceExecutor attacks the code span itself. Section 4 below sends backticks
73 // through the grant sentence, but that path escapes the value directly. The
74 // creation realm is clamped first and escaped second, so it needs its own
75 // case: the fence is chosen after the cut, and must still outscan whatever
76 // backticks survived it. The run here is two long, so a two-backtick fence
77 // would be closed by the payload.
78 type fenceExecutor struct {}
79
80 func (e *fenceExecutor) Execute(cur realm) error { return nil }
81
82 func (e *fenceExecutor) String() string { return "" }
83
84 func (e *fenceExecutor) CreationRealm() string { return "gno.land/r/evil`` **INJECTED**" }
85
86 // wsExecutor returns a large run of spaces. TrimSpace used to run before the
87 // clamp, so it scanned every byte of whatever the executor returned: 250KB of
88 // spaces cost 1,368,719,824 gas to render nothing at all, and 560KB cost
89 // 3,048,904,520 — past the query cap, so the page could not be rendered by
90 // anyone. Clamping first bounds the scan; the same 560KB now costs 16,657,792.
91 type wsExecutor struct {}
92
93 func (e *wsExecutor) Execute(cur realm) error { return nil }
94
95 func (e *wsExecutor) String() string { return "" }
96
97 func (e *wsExecutor) CreationRealm() string { return strings.Repeat( " " , 20000 ) }
98
99 func main(cur realm) {
100 testing.SetOriginCaller(user)
101
102 // 1. An executor with NO description. The creation realm used to share the
103 // `ExecutorString() != ""` gate with the description, so it was hidden for
104 // every such proposal — 16 call sites across 7 production realms.
105 // SetRealm first: NewSimpleExecutor captures rlm.PkgPath(), which is empty
106 // outside a code realm.
107 testing.SetRealm(testing.NewCodeRealm( "gno.land/r/template/silent" ))
108 silent := dao.NewSimpleExecutor( 0 , cur, func (realm) error { return nil }, "" )
109
110 testing.SetRealm(testing.NewUserRealm(user))
111 pid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
112 "Silent" , "A proposal whose executor has no description" , silent))
113 out := dao.Render(cross(cur), pid.String())
114
115 println( "empty-description proposal discloses creation realm:" ,
116 strings.Contains(out, "Executor created in: `gno.land/r/template/silent`" ))
117 println( "and prints no empty metadata block:" ,
118 !strings.Contains(out, "This proposal contains the following metadata" ))
119
120 // 2. CreationRealm() is dispatched through the public dao.Executor
121 // interface, so a third-party executor picks its own value — and the
122 // disclosure now renders for every proposal. InlineCode does not delete the
123 // hostile text; it folds it onto one line inside a code span, where it can
124 // no longer forge page structure. So assert structure, not absence.
125 hpid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
126 "Hostile" , "A proposal whose executor forges page structure" , &hostileExecutor{}))
127 hout := dao.Render(cross(cur), hpid.String())
128
129 println( "only the genuine Stats heading exists:" ,
130 strings.Count(hout, "\n### Stats" ) == 1 )
131 println( "no forged acceptance line:" ,
132 !strings.Contains(hout, "\n- **PROPOSAL HAS BEEN ACCEPTED**" ))
133 println( "no forged tally line:" ,
134 !strings.Contains(hout, "\n- YES PERCENT: 100%" ))
135 println( "genuine tally and status still render:" ,
136 strings.Contains(hout, "\n- YES PERCENT: 0%" ) &&
137 strings.Contains(hout, "- **Proposal is open for votes**" ))
138 // One line, inside a code span: the newlines are folded to spaces, so none
139 // of the payload can start a block. (No pad space before the value now —
140 // TrimSpace removes the payload's trailing newline, so InlineCode does not
141 // need to pad the fence.)
142 println( "hostile value is confined to one code-span line:" ,
143 strings.Contains(hout, "Executor created in: `gno.land/r/sys/params ### Stats" ))
144
145 // 3. The upgrade proposal rewrites AllowedDAOs — the sole authorization for
146 // replacing the implementation, mutating the member store and moving
147 // treasury funds. It used to carry an empty description, so the realm
148 // receiving that authority appeared nowhere a voter would read.
149 upid := dao.MustCreateProposal(cross(cur),
150 impl.NewUpgradeDaoImplRequest(cross(cur), impl.NewGovDAO(), "gno.land/r/gov/dao/v1/impl" , "reason" ))
151 uout := dao.Render(cross(cur), upid.String())
152
153 println( "upgrade proposal states the grant:" ,
154 strings.Contains(uout, "may replace the implementation, mutate the member store, or move treasury funds" ))
155 println( "upgrade proposal names the granted realm:" ,
156 strings.Contains(uout, "`gno.land/r/gov/dao/v1/impl`" ))
157
158 // 4. realmPkg is caller-supplied and lands inside a code span. md.EscapeText
159 // was wrong there: CommonMark 6.1 does not process backslash escapes inside
160 // code spans, so it rendered visible backslashes, and a backtick closed the
161 // span early. InlineCode widens the fence instead, so the payload stays
162 // inside it as literal text.
163 bpid := dao.MustCreateProposal(cross(cur),
164 impl.NewUpgradeDaoImplRequest(cross(cur), impl.NewGovDAO(),
165 "gno.land/r/x` **PROPOSAL HAS BEEN ACCEPTED** `" , "reason" ))
166 bout := dao.Render(cross(cur), bpid.String())
167
168 // The payload's own backtick sits INSIDE a widened `` fence, so it renders
169 // as literal code rather than closing the span and freeing the bold text.
170 println( "fence widened to contain the backtick:" ,
171 strings.Contains(bout, "`` gno.land/r/x` **PROPOSAL HAS BEEN ACCEPTED** ` ``" ))
172 println( "no forged acceptance line from the breakout attempt:" ,
173 !strings.Contains(bout, "\n- **PROPOSAL HAS BEEN ACCEPTED**" ))
174 println( "no visible backslashes in the path:" ,
175 !strings.Contains(bout, "gno\\.land" ))
176
177 // 4b. Guarding the raw value would print the label with nothing after it
178 // for a creation realm that sanitizes away entirely.
179 zpid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
180 "Blank" , "A proposal whose creation realm strips to nothing" , &blankExecutor{}))
181 zout := dao.Render(cross(cur), zpid.String())
182
183 println( "a creation realm that strips to nothing prints no bare label:" ,
184 !strings.Contains(zout, "Executor created in:" ))
185
186 wpid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
187 "Whitespace" , "A proposal whose creation realm is only whitespace" , &whitespaceExecutor{}))
188 println( "a whitespace-only creation realm prints no bare label:" ,
189 !strings.Contains(dao.Render(cross(cur), wpid.String()), "Executor created in:" ))
190
191 hupid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
192 "Huge" , "A proposal whose creation realm is enormous" , &hugeExecutor{}))
193 hurendered := dao.Render(cross(cur), hupid.String())
194 // The truncation marker must sit INSIDE the code span, so the value ends
195 // with its closing fence. That is what proves the clamp ran before the
196 // escaping and not after. Cutting an already-escaped value slices the
197 // closing fence off and leaves the span hanging open, and a length check
198 // alone cannot tell the two apart — both produce a short string ending in
199 // the marker.
200 println( "an enormous creation realm is clamped before escaping:" ,
201 len(hurendered) < 2000 && strings.Contains(hurendered, "… truncated`" ))
202
203 // 4c. The same breakout attempt through the creation realm, which is
204 // clamped before it is escaped. The fence must be sized from the clamped
205 // string, so it widens to three backticks and the payload stays literal.
206 fpid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
207 "Fence" , "A proposal whose creation realm tries to close the code span" , &fenceExecutor{}))
208 fout := dao.Render(cross(cur), fpid.String())
209
210 println( "fence outscans the payload's backtick run:" ,
211 strings.Contains(fout, "Executor created in: ```gno.land/r/evil`` **INJECTED**```" ))
212 println( "no bold text escapes the code span:" ,
213 !strings.Contains(fout, "\n**INJECTED**" ))
214
215 // 4d. A large all-whitespace creation realm. Clamping before trimming is
216 // what bounds the work here, and the marker is how the test can tell: cut
217 // first and the marker survives the trim, so the label renders. Trim first
218 // and the value collapses to nothing, printing no label — and the trim has
219 // already walked every byte the executor produced.
220 wpid2 := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
221 "BigWhitespace" , "A proposal whose creation realm is a huge run of spaces" , &wsExecutor{}))
222 wout2 := dao.Render(cross(cur), wpid2.String())
223
224 println( "a huge whitespace creation realm is clamped before trimming:" ,
225 strings.Contains(wout2, "Executor created in: `… truncated`" ))
226
227 // 5. Proposals live on the proxy but their voting status lives on the
228 // GovDAO instance, so replacing the implementation leaves earlier
229 // proposals renderable but statusless. renderProposalPage took a
230 // user-supplied pid and dereferenced that nil status.
231 testing.SetRealm(testing.NewCodeRealm( "gno.land/r/gov/dao/impl/v0" ))
232 dao.UpdateImpl(cross(cur), dao.NewUpdateRequest(impl.NewGovDAO(), nil ))
233
234 testing.SetRealm(testing.NewUserRealm(user))
235 println( "a proposal orphaned by an upgrade renders instead of panicking:" ,
236 strings.Contains(dao.Render(cross(cur), pid.String()), "not available" ))
237 }
238
239 // Output:
240 // empty-description proposal discloses creation realm: true
241 // and prints no empty metadata block: true
242 // only the genuine Stats heading exists: true
243 // no forged acceptance line: true
244 // no forged tally line: true
245 // genuine tally and status still render: true
246 // hostile value is confined to one code-span line: true
247 // upgrade proposal states the grant: true
248 // upgrade proposal names the granted realm: true
249 // fence widened to contain the backtick: true
250 // no forged acceptance line from the breakout attempt: true
251 // no visible backslashes in the path: true
252 // a creation realm that strips to nothing prints no bare label: true
253 // a whitespace-only creation realm prints no bare label: true
254 // an enormous creation realm is clamped before escaping: true
255 // fence outscans the payload's backtick run: true
256 // no bold text escapes the code span: true
257 // a huge whitespace creation realm is clamped before trimming: true
258 // a proposal orphaned by an upgrade renders instead of panicking: true
259