Kind Realm (renderable)
Name params
Namespace sys
Exported functions 33
Module gno.land/r/sys/params
gno 0.9 delegate_test.gno gno
⧉
1 package params 2 3 import ( 4 "testing" 5 6 prms "sys/params" 7 8 AddRunSubmitters (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}, addrs []string)
GetRunSubmitters () []string
GetValoperRegisterFee () uint64
GetValoperRotationFee () uint64
GetValoperRotationPeriodBlocks () int64
GetValsetEffective () []gno.land/p/sys/validators/v0.Validator
GetValsetEntries () []gno.land/p/sys/validators/v0.Validator
GetValsetPubKeyTypes () []string
IsRunSubmittersDelegate (pkgpath string) bool
NewSetFeeCollectorRequest (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) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSetHaltRequest (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}, height int64, minVersion string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamBoolPropRequest (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}, module string, submodule string, name string, value bool) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamBytesPropRequest (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}, module string, submodule string, name string, value []uint8) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamInt64PropRequest (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}, module string, submodule string, name string, value int64) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamStringPropRequest (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}, module string, submodule string, name string, value string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamStringsPropRequest (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}, module string, submodule string, name string, value []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamStringsPropRequestAddWithTitle (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}, module string, submodule string, name string, title string, value []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamStringsPropRequestRemoveWithTitle (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}, module string, submodule string, name string, title string, value []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamStringsPropRequestWithTitle (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}, module string, submodule string, name string, title string, value []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewSysParamUint64PropRequest (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}, module string, submodule string, name string, value uint64) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeAddUnrestrictedAcctsRequest (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}, addrs ....uverse.address) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeClearRunSubmittersManager (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}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeLockTransferRequest (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}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeRemoveUnrestrictedAcctsRequest (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}, addrs ....uverse.address) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeSetRunSubmitters (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}, addrs []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeSetRunSubmittersManager (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}, pkgpath string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
ProposeUnlockTransferRequest (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}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
RemoveRunSubmitters (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}, addrs []string)
Render (path string) string
RunSubmittersGrantedBy (addr string) bool
RunSubmittersManager () string
SetValsetProposal (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}, entries []string)
ValsetDirty () bool
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
"gno.land/p/moul/addrset/v0"
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11 )
12
13 const (
14 testDelegate = "gno.land/r/test/delegate"
15 testOther = "gno.land/r/test/other"
16 // A sub-realm identity, the form a single DAO hosted by a multi-tenant
17 // realm presents. "#" cannot occur in a real package path.
18 testSubDelegate = "gno.land/r/nt/commondao/v0#dao/42"
19 )
20
21 // resetDelegation returns the slot to undelegated. The realm's tests share
22 // package state, so anything touching the slot must put it back.
23 func resetDelegation() {
24 runSubmittersMgr = ""
25 runSubmittersGrants = addrset.Set{}
26 }
27
28 // armRunSubmitters puts addresses on the list the way GovDAO does: by writing
29 // the parameter, not through the delegate.
30 //
31 // Tests need it because the delegate may no longer arm an empty list. An empty
32 // run_submitters means the gate is off, so the first add would switch a
33 // chain-wide restriction on rather than curate one, and that is a vote.
34 func armRunSubmitters(addrs ...string) {
35 prms.UpdateSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey, addrs, true )
36 }
37
38 // unarmRunSubmitters drops addresses the way a GovDAO vote does, bypassing the
39 // delegate's grant-scoping. Used to build a list whose every remaining entry was
40 // granted by the delegate, which is the only state where the non-empty floor is
41 // the binding constraint.
42 func unarmRunSubmitters(addrs ...string) {
43 prms.UpdateSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey, addrs, false )
44 }
45
46 // TestRunSubmittersUndelegatedDeniesEveryone is the single most important test
47 // here: with no delegate configured, the slot is "" and a direct user call also
48 // presents "". A gate that compared the two without checking for empty first
49 // would admit every account on the chain.
50 func TestRunSubmittersUndelegatedDeniesEveryone(cur realm, t *testing.T) {
51 resetDelegation()
52 defer resetDelegation()
53
54 uassert.Equal(t, "" , RunSubmittersManager())
55
56 // A user account: PkgPath() is empty, matching the empty slot.
57 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "nobody" )))
58 uassert.AbortsContains(t, cur, "no delegate is configured" , func () {
59 AddRunSubmitters(cross(cur), [] string {testutils.TestAddress( "victim" ).String()})
60 })
61
62 // And a code realm, for completeness.
63 testing.SetRealm(testing.NewCodeRealm(testOther))
64 uassert.AbortsContains(t, cur, "no delegate is configured" , func () {
65 AddRunSubmitters(cross(cur), [] string {testutils.TestAddress( "victim" ).String()})
66 })
67 }
68
69 // TestRunSubmittersOnlyTheDelegateMayWrite pins that authorization is by exact
70 // package path.
71 func TestRunSubmittersOnlyTheDelegateMayWrite(cur realm, t *testing.T) {
72 resetDelegation()
73 defer resetDelegation()
74
75 runSubmittersMgr = testDelegate
76 armRunSubmitters(testutils.TestAddress( "seeded-by-vote" ).String())
77 addr := testutils.TestAddress( "granted" ).String()
78
79 // A different realm is refused.
80 testing.SetRealm(testing.NewCodeRealm(testOther))
81 uassert.AbortsContains(t, cur, "unauthorized" , func () {
82 AddRunSubmitters(cross(cur), [] string {addr})
83 })
84
85 // A user account is refused: an empty path must not match a set slot.
86 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "nobody" )))
87 uassert.AbortsContains(t, cur, "unauthorized" , func () {
88 AddRunSubmitters(cross(cur), [] string {addr})
89 })
90
91 // The delegate itself succeeds.
92 testing.SetRealm(testing.NewCodeRealm(testDelegate))
93 AddRunSubmitters(cross(cur), [] string {addr})
94 uassert.True(t, contains(GetRunSubmitters(), addr),
95 "the delegate's addition must reach the parameter" )
96 }
97
98 // TestRunSubmittersSubRealmIdentityIsExact pins that delegate matching is exact
99 // string equality, which is what makes a per-DAO delegation safe.
100 //
101 // A sub-realm identity minted by cur.Sub(subpath) presents "host#subpath", so a
102 // single DAO hosted by a multi-tenant realm can hold the capability. Matching by
103 // prefix instead would hand it to every DAO that host serves -- and CommonDAO
104 // membership, while invite-gated, is unlimited once invited.
105 //
106 // Asserted through the pure predicate rather than by crossing: constructing a
107 // live sub-realm cur is not something the test harness can do (NewCodeRealm
108 // rejects "#", and MakeRealm does not satisfy IsCurrent), and the comparison
109 // under test is the same one assertDelegate performs.
110 func TestRunSubmittersSubRealmIdentityIsExact(t *testing.T) {
111 resetDelegation()
112 defer resetDelegation()
113
114 runSubmittersMgr = testSubDelegate
115
116 uassert.True(t, IsRunSubmittersDelegate(testSubDelegate),
117 "the named DAO holds the capability" )
118 uassert.False(t, IsRunSubmittersDelegate( "gno.land/r/nt/commondao/v0" ),
119 "the host realm must not inherit its sub-identity's authority" )
120 uassert.False(t, IsRunSubmittersDelegate( "gno.land/r/nt/commondao/v0#dao/43" ),
121 "a sibling DAO of the same host must not match" )
122 uassert.False(t, IsRunSubmittersDelegate( "gno.land/r/nt/commondao/v0#dao/4" ),
123 "a prefix of the subpath must not match" )
124 uassert.False(t, IsRunSubmittersDelegate( "" ),
125 "an empty path must never match a configured delegate" )
126 }
127
128 // TestRunSubmittersRemoveIsScopedToOwnGrants pins that the delegate cannot
129 // remove an address it did not add, so a list GovDAO curated survives a rogue
130 // delegate. The companion bound -- that it can never take the list to zero by
131 // any route -- is TestRunSubmittersCannotEmptyTheList.
132 func TestRunSubmittersRemoveIsScopedToOwnGrants(cur realm, t *testing.T) {
133 resetDelegation()
134 defer resetDelegation()
135
136 preexisting := testutils.TestAddress( "breakglass" ).String()
137 testing.SetSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey,
138 [] string {preexisting})
139
140 runSubmittersMgr = testDelegate
141 armRunSubmitters(testutils.TestAddress( "seeded-by-vote" ).String())
142 own := testutils.TestAddress( "ownadd" ).String()
143
144 testing.SetRealm(testing.NewCodeRealm(testDelegate))
145 AddRunSubmitters(cross(cur), [] string {own})
146
147 // It may remove what it granted.
148 uassert.True(t, RunSubmittersGrantedBy(address(own)))
149 RemoveRunSubmitters(cross(cur), [] string {own})
150 uassert.False(t, contains(GetRunSubmitters(), own))
151
152 // It may NOT remove the address that predated the delegation.
153 uassert.False(t, RunSubmittersGrantedBy(address(preexisting)))
154 uassert.AbortsContains(t, cur, "only GovDAO may remove it" , func () {
155 RemoveRunSubmitters(cross(cur), [] string {preexisting})
156 })
157 uassert.True(t, contains(GetRunSubmitters(), preexisting),
158 "the pre-existing entry must survive a refused removal" )
159 }
160
161 // TestRunSubmittersCannotEmptyTheList pins the non-empty floor.
162 //
163 // An empty run_submitters means the gate is OFF: anyone on the chain may
164 // MsgRun. So emptying the list is not a smaller version of removing one
165 // address, it is the unilateral revocation of the whole restriction GovDAO
166 // voted for -- the one thing this capability must not be able to do.
167 //
168 // Grant-scoping alone does not prevent it. It holds only while an entry the
169 // delegate did not grant survives, and GovDAO replacing the list wholesale can
170 // remove its own entries without touching the grant record, which is exactly
171 // the state set up below.
172 func TestRunSubmittersCannotEmptyTheList(cur realm, t *testing.T) {
173 resetDelegation()
174 defer resetDelegation()
175
176 runSubmittersMgr = testDelegate
177 seed := testutils.TestAddress( "seeded-by-vote" ).String()
178 armRunSubmitters(seed)
179 a := testutils.TestAddress( "granted-a" ).String()
180 b := testutils.TestAddress( "granted-b" ).String()
181
182 testing.SetRealm(testing.NewCodeRealm(testDelegate))
183 AddRunSubmitters(cross(cur), [] string {a, b})
184
185 // GovDAO then drops its own seed by vote, which grant-scoping does not
186 // constrain. This is the only way to reach a list whose every entry was
187 // granted by the delegate, now that the delegate cannot arm an empty one --
188 // and it is the state where the floor is the last thing standing.
189 unarmRunSubmitters(seed)
190 uassert.Equal(t, 2 , len(GetRunSubmitters()))
191
192 // Every listed address is now one the delegate granted, so grant-scoping
193 // permits removing all of them. The floor is the only thing left.
194 uassert.True(t, RunSubmittersGrantedBy(address(a)))
195 uassert.True(t, RunSubmittersGrantedBy(address(b)))
196
197 // Down to one is fine: shrinking the list is the delegate's job.
198 RemoveRunSubmitters(cross(cur), [] string {a})
199 uassert.Equal(t, 1 , len(GetRunSubmitters()))
200
201 // The last one is not.
202 uassert.AbortsContains(t, cur, "refusing to empty" , func () {
203 RemoveRunSubmitters(cross(cur), [] string {b})
204 })
205 uassert.True(t, contains(GetRunSubmitters(), b),
206 "the last entry must survive a refused removal" )
207
208 // Nor in one call, and nor by naming addresses that are not listed: the
209 // count is taken against the parameter, not the argument.
210 uassert.AbortsContains(t, cur, "refusing to empty" , func () {
211 RemoveRunSubmitters(cross(cur), [] string {b, b})
212 })
213 uassert.True(t, contains(GetRunSubmitters(), b))
214
215 // And the delegate is not locked out of its ordinary work: adding still
216 // works, and once there are two again it may remove one.
217 AddRunSubmitters(cross(cur), [] string {a})
218 RemoveRunSubmitters(cross(cur), [] string {b})
219 uassert.True(t, contains(GetRunSubmitters(), a))
220 uassert.False(t, contains(GetRunSubmitters(), b))
221 }
222
223 // TestRunSubmittersRevocationDropsGrantRecord pins that a new holder does not
224 // inherit removal authority over its predecessor's grants.
225 func TestRunSubmittersRevocationDropsGrantRecord(cur realm, t *testing.T) {
226 resetDelegation()
227 defer resetDelegation()
228
229 runSubmittersMgr = testDelegate
230 armRunSubmitters(testutils.TestAddress( "seeded-by-vote" ).String())
231 addr := testutils.TestAddress( "byfirst" ).String()
232 testing.SetRealm(testing.NewCodeRealm(testDelegate))
233 AddRunSubmitters(cross(cur), [] string {addr})
234 uassert.True(t, RunSubmittersGrantedBy(address(addr)))
235
236 // Hand the capability over, as the proposal executor would.
237 runSubmittersMgr = testOther
238 runSubmittersGrants = addrset.Set{}
239
240 testing.SetRealm(testing.NewCodeRealm(testOther))
241 uassert.False(t, RunSubmittersGrantedBy(address(addr)))
242 uassert.AbortsContains(t, cur, "only GovDAO may remove it" , func () {
243 RemoveRunSubmitters(cross(cur), [] string {addr})
244 })
245 }
246
247 // TestDelegatePathValidation pins what may be stored in a slot. An ephemeral
248 // `maketx run` realm has a NON-empty path, so a check for "is this code" would
249 // let one through; only the gno.land/r/ requirement excludes it.
250 func TestDelegatePathValidation(t *testing.T) {
251 for _, bad := range [] string {
252 "" ,
253 "gno.land/p/nt/avl/v0" ,
254 "gno.land/e/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/run" ,
255 "gno.land/r/" ,
256 // Paths no caller could ever present. Storing one would leave GovDAO
257 // believing it delegated while the delegate is refused on every call.
258 "gno.land/r/test/delegate " , // trailing space
259 " gno.land/r/test/delegate" , // leading space
260 "gno.land/r/nt/commondao/v0#DAO/42" , // uppercase subpath
261 "gno.land/r/nt/commondao/v0#dao/42#dao/43" , // two separators
262 } {
263 // PanicsContains, not AbortsContains: this is a same-realm call, so
264 // the panic never crosses a realm boundary and is not an abort.
265 uassert.PanicsContains(t, cur, "invalid delegate" , func () {
266 assertDelegatePath(bad)
267 })
268 }
269 // Sound paths, including a sub-realm identity.
270 assertDelegatePath(testDelegate)
271 assertDelegatePath(testSubDelegate)
272 }
273
274 // TestValsetGateRejectsForeignRealm closes a pre-existing coverage gap found
275 // while factoring the shared gate: assertValsetCaller had no test in this realm
276 // at all, so nothing asserted that a realm other than r/sys/validators/v0 is
277 // refused.
278 func TestValsetGateRejectsForeignRealm(cur realm, t *testing.T) {
279 testing.SetRealm(testing.NewCodeRealm(testOther))
280 uassert.AbortsContains(t, cur, "unauthorized" , func () {
281 SetValsetProposal(cross(cur), [] string { "somepubkey:1" })
282 })
283
284 // A user account must be refused identically -- the empty-path case.
285 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "nobody" )))
286 uassert.AbortsContains(t, cur, "unauthorized" , func () {
287 SetValsetProposal(cross(cur), [] string { "somepubkey:1" })
288 })
289 }
290
291 // TestAssertNotValsetKeyRejectsGenericFactory closes the other gap: the guard
292 // whose own comment says it stops "any GovDAO supermajority" from writing
293 // validator-set state through the generic param factory had no test.
294 func TestAssertNotValsetKeyRejectsGenericFactory(cur realm, t *testing.T) {
295 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "proposer" )))
296 uassert.AbortsContains(t, cur, "reserved for" , func () {
297 NewSysParamStringsPropRequest(cross(cur), "node" , "valset" , "proposed" ,
298 [] string { "somepubkey:1" })
299 })
300 }
301
302 func contains(haystack [] string , needle string ) bool {
303 for _, h := range haystack {
304 if h == needle {
305 return true
306 }
307 }
308 return false
309 }
310
311 // TestRunSubmittersGrantLaunderingIsRefused reproduces a real hole in the first
312 // version of this code, found by audit.
313 //
314 // UpdateSysParamStrings dedupes on add, so re-adding an address already on the
315 // list is a no-op on the parameter. The first implementation recorded a grant
316 // for every argument regardless, which let the delegate launder authority over
317 // entries it never granted:
318 //
319 // read the list -> re-add all of it -> remove all of it
320 //
321 // The parameter never changed on the middle step, but every address became
322 // "granted by me", so the removal was permitted and the allowlist ended empty --
323 // including the entry that predated the delegation. Since GovDAO proposal
324 // creation is MsgRun-only, an empty run_submitters means no proposal can be
325 // created to revoke the delegate or restore the list. That is a chain brick
326 // recoverable only by relaunch.
327 func TestRunSubmittersGrantLaunderingIsRefused(cur realm, t *testing.T) {
328 resetDelegation()
329 defer resetDelegation()
330
331 breakglass := testutils.TestAddress( "breakglass2" ).String()
332 testing.SetSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey,
333 [] string {breakglass})
334
335 runSubmittersMgr = testDelegate
336 armRunSubmitters(testutils.TestAddress( "seeded-by-vote" ).String())
337 testing.SetRealm(testing.NewCodeRealm(testDelegate))
338
339 // Step 1: re-add what is already there. A no-op on the parameter, and it
340 // must NOT create a grant.
341 AddRunSubmitters(cross(cur), GetRunSubmitters())
342 uassert.False(t, RunSubmittersGrantedBy(address(breakglass)),
343 "re-adding an existing address must not record a grant for it" )
344
345 // Step 2: the removal must therefore still be refused.
346 uassert.AbortsContains(t, cur, "only GovDAO may remove it" , func () {
347 RemoveRunSubmitters(cross(cur), [] string {breakglass})
348 })
349 uassert.True(t, contains(GetRunSubmitters(), breakglass),
350 "the pre-existing entry must survive" )
351
352 // And the whole-list version of the same attack, which is how it would
353 // actually be run.
354 AddRunSubmitters(cross(cur), GetRunSubmitters())
355 uassert.AbortsContains(t, cur, "only GovDAO may remove it" , func () {
356 RemoveRunSubmitters(cross(cur), GetRunSubmitters())
357 })
358 uassert.True(t, len(GetRunSubmitters()) > 0 ,
359 "the allowlist must never end up empty at a delegate's hand" )
360
361 // A genuinely new address still works normally.
362 fresh := testutils.TestAddress( "freshgrant" ).String()
363 AddRunSubmitters(cross(cur), [] string {fresh})
364 uassert.True(t, RunSubmittersGrantedBy(address(fresh)))
365 RemoveRunSubmitters(cross(cur), [] string {fresh})
366 uassert.False(t, contains(GetRunSubmitters(), fresh))
367 }
368
369 // TestRenderShowsDelegationState pins that the page actually reports the state
370 // it exists to report.
371 //
372 // The value of a Render here is that someone auditing the chain can see whether
373 // a parameter is delegated without knowing to ask. A page that renders the same
374 // text whether or not a delegation exists would defeat that, so both states are
375 // checked.
376 func TestRenderShowsDelegationState(t *testing.T) {
377 resetDelegation()
378 defer resetDelegation()
379
380 addr := testutils.TestAddress( "rendered" ).String()
381 testing.SetSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey,
382 [] string {addr})
383
384 // Undelegated: it must say so rather than leave the reader guessing.
385 out := Render( "" )
386 uassert.True(t, contains2(out, "nobody" ),
387 "an undelegated parameter must be reported as such" )
388 uassert.False(t, contains2(out, testDelegate),
389 "no delegate should be named when none is set" )
390
391 // Delegated: the holder is named.
392 runSubmittersMgr = testDelegate
393 armRunSubmitters(testutils.TestAddress( "seeded-by-vote" ).String())
394 out = Render( "" )
395 uassert.True(t, contains2(out, testDelegate),
396 "the delegate holding the capability must be named" )
397 uassert.True(t, contains2(out, addr),
398 "the addresses the parameter currently allows must be listed" )
399
400 // The valset writer is fixed in source, not delegated, and the page should
401 // not blur the two.
402 uassert.True(t, contains2(out, valsetAuthorizedRealm),
403 "the realm that writes valset params must be shown too" )
404 }
405
406 // contains2 reports whether s contains sub.
407 func contains2(s, sub string ) bool {
408 if len(sub) == 0 {
409 return true
410 }
411 for i := 0 ; i+len(sub) <= len(s); i++ {
412 if s[i:i+len(sub)] == sub {
413 return true
414 }
415 }
416 return false
417 }
418
419 // TestRunSubmittersDelegateCannotArmTheGate covers the direction the non-empty
420 // floor does not: turning the allowlist ON.
421 //
422 // An empty run_submitters means the gate is off and anyone may MsgRun. So a
423 // delegate adding the first address is not curating a list, it is switching a
424 // chain-wide restriction on and choosing who it admits -- leaving that address
425 // the only one on the chain that may run code.
426 //
427 // That is unrepairable in band. Creating a GovDAO proposal needs MsgRun, so
428 // once the gate is armed against the members they cannot propose the vote that
429 // would reset the list or revoke the delegation, and the floor in
430 // RemoveRunSubmitters stops the delegate from undoing it either.
431 func TestRunSubmittersDelegateCannotArmTheGate(cur realm, t *testing.T) {
432 resetDelegation()
433 defer resetDelegation()
434
435 runSubmittersMgr = testDelegate
436 attacker := testutils.TestAddress( "attacker" ).String()
437
438 // The gate starts off.
439 uassert.Equal(t, 0 , len(GetRunSubmitters()))
440
441 testing.SetRealm(testing.NewCodeRealm(testDelegate))
442 uassert.AbortsContains(t, cur, "refusing to arm" , func () {
443 AddRunSubmitters(cross(cur), [] string {attacker})
444 })
445 uassert.Equal(t, 0 , len(GetRunSubmitters()),
446 "a refused arm must leave the gate off" )
447
448 // Once GovDAO has armed it, the delegate may curate as before.
449 seeded := testutils.TestAddress( "seeded-by-vote" ).String()
450 armRunSubmitters(seeded)
451 testing.SetRealm(testing.NewCodeRealm(testDelegate))
452 AddRunSubmitters(cross(cur), [] string {attacker})
453 uassert.Equal(t, 2 , len(GetRunSubmitters()),
454 "curating an armed list is still the delegate's job" )
455 }
456
457 // TestRunSubmittersKeyIsReservedFromGenericFactories pins that the whole-list
458 // path is the dedicated constructor and nothing else.
459 //
460 // The generic factories take module, submodule and name as arguments, so
461 // without this any of the nine could write run_submitters and walk past the
462 // proposer rule in ProposeSetRunSubmitters. Same shape as the valset
463 // reservation next to it.
464 func TestRunSubmittersKeyIsReservedFromGenericFactories(cur realm, t *testing.T) {
465 addr := testutils.TestAddress( "someone" ).String()
466
467 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
468 NewSysParamStringsPropRequest(cross(cur), "vm" , "p" , runSubmittersKey, [] string {addr})
469 })
470 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
471 NewSysParamStringsPropRequestWithTitle(cross(cur), "vm" , "p" , runSubmittersKey, "t" , [] string {addr})
472 })
473 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
474 NewSysParamStringsPropRequestAddWithTitle(cross(cur), "vm" , "p" , runSubmittersKey, "t" , [] string {addr})
475 })
476 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
477 NewSysParamStringsPropRequestRemoveWithTitle(cross(cur), "vm" , "p" , runSubmittersKey, "t" , [] string {addr})
478 })
479 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
480 NewSysParamStringPropRequest(cross(cur), "vm" , "p" , runSubmittersKey, addr)
481 })
482
483 // The four typed factories too. They cannot carry a run_submitters value,
484 // but they can name the key, and the reservation is about the key -- all
485 // nine share one check on the funnel they return through, so all nine are
486 // listed here rather than the five that happen to take strings.
487 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
488 NewSysParamInt64PropRequest(cross(cur), "vm" , "p" , runSubmittersKey, 1 )
489 })
490 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
491 NewSysParamUint64PropRequest(cross(cur), "vm" , "p" , runSubmittersKey, 1 )
492 })
493 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
494 NewSysParamBoolPropRequest(cross(cur), "vm" , "p" , runSubmittersKey, true )
495 })
496 uassert.AbortsContains(t, cur, "reserved for ProposeSetRunSubmitters" , func () {
497 NewSysParamBytesPropRequest(cross(cur), "vm" , "p" , runSubmittersKey, [] byte { 1 })
498 })
499
500 // A different key in the same module is unaffected.
501 uassert.NotAborts(t, cur, func () {
502 NewSysParamStringsPropRequest(cross(cur), "vm" , "p" , "code_submitters" , [] string {addr})
503 })
504 }
505
506 // TestProposeSetRunSubmittersRequiresProposerOnTheList covers the rule that a
507 // non-empty allowlist must include whoever proposed it.
508 //
509 // A list naming nobody who can create a proposal cannot be undone, because
510 // creating one needs MsgRun. Requiring the proposer's own address proves the
511 // list is usable: GovDAO refuses a proposal from a non-member, and the proposer
512 // just signed the transaction, so that address demonstrably holds a key.
513 func TestProposeSetRunSubmittersRequiresProposerOnTheList(cur realm, t *testing.T) {
514 other := testutils.TestAddress( "someone-else" ).String()
515
516 // A list without the proposer is refused.
517 uassert.AbortsContains(t, cur, "omits the proposer" , func () {
518 ProposeSetRunSubmitters(cross(cur), [] string {other})
519 })
520
521 // Emptying the list is always allowed: that switches the gate off, which
522 // cannot lock anyone out.
523 uassert.NotAborts(t, cur, func () {
524 ProposeSetRunSubmitters(cross(cur), [] string {})
525 })
526
527 // A list that does include the proposer goes through.
528 proposer := testutils.TestAddress( "proposer" )
529 testing.SetRealm(testing.NewUserRealm(proposer))
530 uassert.NotAborts(t, cur, func () {
531 ProposeSetRunSubmitters(cross(cur), [] string {other, proposer.String()})
532 })
533 }
534
535 // TestSetRunSubmittersReplacesTheList covers what the whole-list setter does to
536 // a list that is not empty.
537 //
538 // This realm reserves run_submitters from the generic factories, so its own
539 // setter is the only route by vote. If that setter appended instead of
540 // replacing, the parameter would be append-only chain-wide: a compromised
541 // address could never be de-listed and the gate could never be turned back off,
542 // while the proposal shown to voters would say otherwise.
543 //
544 // Every other test here starts from an empty list, where appending and
545 // replacing look identical, which is why this one starts armed.
546 func TestSetRunSubmittersReplacesTheList(cur realm, t *testing.T) {
547 resetDelegation()
548 defer resetDelegation()
549
550 a := testutils.TestAddress( "keep-a" ).String()
551 b := testutils.TestAddress( "drop-b" ).String()
552 armRunSubmitters(a, b)
553 uassert.Equal(t, 2 , len(GetRunSubmitters()))
554
555 // Dropping b must actually drop it.
556 setRunSubmitters([] string {a})
557 got := GetRunSubmitters()
558 uassert.Equal(t, 1 , len(got), "the list must be replaced, not appended to" )
559 uassert.True(t, contains(got, a))
560 uassert.False(t, contains(got, b), "a de-listed address must be gone" )
561
562 // And emptying must switch the gate off, which is what the proposal says.
563 setRunSubmitters([] string {})
564 uassert.Equal(t, 0 , len(GetRunSubmitters()),
565 "an empty list must be reachable, or the gate can never be turned off" )
566 }
567
568 // TestAssertDelegateRejectsANonLiveRealm covers the IsCurrent check in
569 // assertDelegate.
570 //
571 // This is the shape where the check can actually fire. assertDelegate takes its
572 // realm as an ordinary parameter, so a caller can hand it a stashed or
573 // sibling-frame value; without IsCurrent it would then compare that value's
574 // Previous() against the delegate path and admit whoever assembled it.
575 //
576 // Crossing functions cannot be tested this way, and do not need to be: the
577 // compiler refuses anything but `cur` or `cross(rlm)` as their first argument,
578 // so their cur is live by construction.
579 func TestAssertDelegateRejectsANonLiveRealm(cur realm, t *testing.T) {
580 resetDelegation()
581 defer resetDelegation()
582
583 runSubmittersMgr = testDelegate
584
585 // A synthetic realm whose Previous() names the delegate. The path check
586 // alone would admit it; IsCurrent is the only thing that does not.
587 fake := testing.MakeRealm(
588 testutils.TestAddress( "impostor" ), "gno.land/r/impostor" ,
589 testing.MakeRealm(testutils.TestAddress( "d" ), testDelegate, testing.OriginRealm()),
590 )
591 uassert.Equal(t, testDelegate, fake.Previous().PkgPath(),
592 "premise: the forged realm must pass the path check, or this proves nothing" )
593
594 // PanicsContains, not Aborts: assertDelegate is called directly here, in
595 // this realm, so the refusal is a panic. It surfaces as an abort only when
596 // it happens across a realm boundary.
597 uassert.PanicsContains(t, cur, "not the caller's live cur" , func () {
598 assertDelegate( 0 , fake, runSubmittersMgr, "the " +runSubmittersKey+ " allowlist" )
599 })
600 }
601
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.