Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}