delegate_test.gno
24.81 Kb · 600 lines
1package params
2
3import (
4 "testing"
5
6 prms "sys/params"
7
8 "gno.land/p/moul/addrset/v0"
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11)
12
13const (
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.
23func 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.
34func 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.
42func 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.
50func 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.
71func 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.
110func 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.
132func 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.
172func 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.
225func 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.
250func 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.
278func 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.
294func 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
302func 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.
327func 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.
376func 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.
407func 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.
431func 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.
464func 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.
513func 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.
546func 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.
579func 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}