Kind Realm (renderable)
Name params
Namespace sys
Exported functions 33
Module gno.land/r/sys/params
gno 0.9 run_submitters.gno gno
⧉
1 package params 2 3 // Delegated management of vm:p:run_submitters, the allowlist of addresses 4 // permitted to send MsgRun. 5 // 6 // See delegate.gno for why this is a named slot rather than a registry. 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.
// What the delegate can and cannot do, and why the asymmetry is this way round:
9 //
10 // - It may ADD addresses. This is the routine work the delegation exists for.
11 // - It may REMOVE only addresses it added itself, and never the last one.
12 // De-listing its own mistake is a core part of managing an allowlist, so
13 // add-only would be a strange capability to hand out. Two bounds keep that
14 // safe. Grant-scoping stops it removing an address that predates the
15 // delegation. The non-empty floor in RemoveRunSubmitters stops it reaching
16 // zero by any route — an empty list means the gate is OFF and anyone may
17 // MsgRun, so emptying it would let the delegate revoke the entire
18 // restriction GovDAO voted for, which is the one thing this capability must
19 // not be able to do.
20 // - GovDAO retains everything, through ProposeSetRunSubmitters below -- the
21 // generic factories no longer accept this key. That is the bounded reset:
22 // one proposal returns the key to a known-good list regardless of what the
23 // delegate did, and the proposal shows voters the exact resulting list.
24 //
25 // A cost to state plainly rather than bury: this key is read by the ante handler
26 // on EVERY transaction, before the per-tx gas meter exists, so its length is an
27 // unmetered per-transaction constant for the whole chain. The delegate therefore
28 // holds a knob on that constant, bounded only by maxAddressListLen in
29 // gno.land/pkg/sdk/vm/params.go. That bound is enforced on this path for free:
30 // UpdateSysParamStrings re-sets the whole list, which re-enters WillSetParam and
31 // Params.Validate, so both the cap and bech32 validation apply to a delegate's
32 // additions. A key that is not read on the ante path would be a cheaper first
33 // delegation; this one is the one that was asked for.
34
35 import (
36 "chain"
37 prms "sys/params"
38
39 "gno.land/p/moul/addrset/v0"
40
41 "gno.land/r/gov/dao"
42 )
43
44 const (
45 vmModulePrefix = "vm"
46 vmParamsSubmodule = "p"
47
48 runSubmittersKey = "run_submitters"
49 )
50
51 // runSubmittersMgr is the package path authorized to manage run_submitters.
52 // Empty means the capability is not delegated, and empty must deny — see
53 // assertDelegate.
54 var runSubmittersMgr string
55
56 // runSubmittersGrants records which addresses the current delegate added, so
57 // removal can be scoped to its own grants.
58 //
59 // The parameter is the source of truth and this is a side table, so the two can
60 // disagree — genesis, `gnogenesis params set`, or any future direct keeper write
61 // produces entries with no grant recorded. That direction is safe: an unrecorded
62 // address is simply not removable by the delegate, which is the conservative
63 // answer. Cleared whenever the delegation changes hands, so a new delegate never
64 // inherits authority over its predecessor's grants.
65 var runSubmittersGrants = addrset.Set{}
66
67 // RunSubmittersManager returns the package path currently authorized to manage
68 // run_submitters, or "" when the capability is not delegated.
69 func RunSubmittersManager() string {
70 return runSubmittersMgr
71 }
72
73 // RunSubmittersGrantedBy reports whether the current delegate added addr, i.e.
74 // whether it may remove it.
75 //
76 // Exposed so a delegate can check before acting. A delegate that discovers a
77 // refusal by panicking mid-proposal-execution is in a bad place: the panic
78 // aborts the transaction, and for a DAO whose proposal has already passed, every
79 // retry aborts the same way.
80 func RunSubmittersGrantedBy(addr address) bool {
81 return runSubmittersGrants.Has(addr)
82 }
83
84 // IsRunSubmittersDelegate reports whether pkgpath currently holds the
85 // capability. Pure predicate, for a caller that wants to fail cleanly rather
86 // than be panicked at.
87 func IsRunSubmittersDelegate(pkgpath string ) bool {
88 return pkgpath != "" && pkgpath == runSubmittersMgr
89 }
90
91 // ProposeSetRunSubmittersManager creates a GovDAO proposal handing management of
92 // run_submitters to pkgpath.
93 //
94 // pkgpath may be a sub-realm identity such as
95 // "gno.land/r/nt/commondao/v0#dao/42", which is how a single DAO hosted by a
96 // multi-tenant realm is named. Matching is exact, so naming the bare host would
97 // authorize the host itself and none of its DAOs.
98 func ProposeSetRunSubmittersManager(cur realm, pkgpath string ) dao.ProposalRequest {
99 assertDelegatePath(pkgpath)
100 if pkgpath == runSubmittersMgr {
101 panic( "no-op proposal rejected: " + pkgpath + " already manages " + runSubmittersKey)
102 }
103
104 // desc uses the manager as of proposal creation, which is the honest thing
105 // to show a voter. The EVENT reads it again inside the executor, because two
106 // proposals created while the slot is empty and executed in sequence would
107 // otherwise both report from="" while the second actually replaced the first.
108 from := runSubmittersMgr
109 desc := "Authorize " + pkgpath + " to add addresses to the " + runSubmittersKey +
110 " allowlist, which gates who may send MsgRun. It may remove only " +
111 "addresses it added itself. GovDAO retains full control, including " +
112 "replacing the whole list."
113 if from != "" {
114 desc += " This replaces the current manager, " + from +
115 ", and discards the record of which addresses it granted."
116 }
117
118 return newDelegateProposal(cur, DelegateSetEvent, runSubmittersKey, pkgpath,
119 "Delegate " +runSubmittersKey+ " management" , desc,
120 func () string {
121 prev := runSubmittersMgr
122 runSubmittersMgr = pkgpath
123 // A new holder must not inherit removal authority over addresses
124 // the previous one granted.
125 runSubmittersGrants = addrset.Set{}
126 return prev
127 })
128 }
129
130 // ProposeClearRunSubmittersManager creates a GovDAO proposal revoking the
131 // delegation.
132 //
133 // Revocation is immediate on execution because the slot is consulted on every
134 // call. It deliberately does NOT remove addresses the delegate added: sweeping
135 // them would make the executed effect invisible at vote time, and would silently
136 // remove nothing whenever the grant record had drifted. Use the existing
137 // whole-list setter to reset the list to a reviewed value.
138 func ProposeClearRunSubmittersManager(cur realm) dao.ProposalRequest {
139 if runSubmittersMgr == "" {
140 panic( "no-op proposal rejected: " + runSubmittersKey + " is not delegated" )
141 }
142
143 from := runSubmittersMgr
144 return newDelegateProposal(cur, DelegateClearedEvent, runSubmittersKey, "" ,
145 "Revoke " +runSubmittersKey+ " management" ,
146 "Revoke " +from+ "'s authority to manage the " +runSubmittersKey+
147 " allowlist. Addresses it already added REMAIN on the list; reset the " +
148 "list explicitly if that is not wanted." ,
149 func () string {
150 prev := runSubmittersMgr
151 runSubmittersMgr = ""
152 runSubmittersGrants = addrset.Set{}
153 return prev
154 })
155 }
156
157 // AddRunSubmitters adds addresses to the run_submitters allowlist.
158 //
159 // Callable only by the delegated manager. Addresses already present are a no-op
160 // (UpdateSysParamStrings dedupes), and the chain still validates every entry and
161 // enforces the list-length cap, because the update re-sets the whole list.
162 func AddRunSubmitters(cur realm, addrs [] string ) {
163 assertDelegate( 0 , cur, runSubmittersMgr, "the " +runSubmittersKey+ " allowlist" )
164 if len(addrs) == 0 {
165 return
166 }
167
168 // The delegate may curate a list that is already in force. It may not put
169 // one into force.
170 //
171 // An empty run_submitters means the allowlist is OFF and anyone may MsgRun.
172 // So the first add is not curation: it switches a chain-wide restriction on
173 // and picks who it admits. A delegate adding one address to an empty list
174 // leaves that address the only one on the chain that may run code.
175 //
176 // Refused rather than discouraged because it is unrepairable. Creating a
177 // GovDAO proposal needs MsgRun -- a ProposalRequest carries an Executor,
178 // which MsgCall cannot build from string arguments -- so once the gate is
179 // armed against the members they cannot propose the vote that would undo
180 // it, and the floor in RemoveRunSubmitters stops the delegate undoing it
181 // either.
182 if len(GetRunSubmitters()) == 0 {
183 panic( "refusing to arm the " + runSubmittersKey +
184 " allowlist: it is empty, so the gate is off and anyone may MsgRun. " +
185 "Turning it on is a GovDAO vote, not a delegated edit" )
186 }
187
188 // Record a grant only for an address this call actually ADDED.
189 //
190 // UpdateSysParamStrings dedupes on add, so passing an address already on the
191 // list leaves the parameter unchanged. Recording a grant for it anyway would
192 // let the delegate launder authority over entries it never granted: read the
193 // list, re-add all of it (a no-op on the parameter, but every address now
194 // recorded as its own), then remove all of it -- including whatever predated
195 // the delegation. The floor in RemoveRunSubmitters would refuse the last of
196 // those removals, but only the last: everything up to it would still go
197 // through, leaving the delegate holding the only listed address and so the
198 // sole authority over who may MsgRun.
199 //
200 // So the grant record has to follow the parameter, not the argument.
201 present := make( map [ string ] bool )
202 for _, a := range GetRunSubmitters() {
203 present[a] = true
204 }
205
206 prms.UpdateSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey, addrs, true )
207 for _, a := range addrs {
208 if !present[a] {
209 runSubmittersGrants.Add(address(a))
210 }
211 }
212 chain.Emit(DelegateWriteEvent,
213 "key" , runSubmittersKey, "realm" , runSubmittersMgr, "op" , "add" )
214 }
215
216 // RemoveRunSubmitters removes addresses from the run_submitters allowlist.
217 //
218 // Callable only by the delegated manager, and only for addresses that manager
219 // added. Refusing rather than silently skipping is deliberate: a partial removal
220 // that reported success would leave the caller believing an address was
221 // de-listed when it was not.
222 func RemoveRunSubmitters(cur realm, addrs [] string ) {
223 assertDelegate( 0 , cur, runSubmittersMgr, "the " +runSubmittersKey+ " allowlist" )
224 if len(addrs) == 0 {
225 return
226 }
227 for _, a := range addrs {
228 if !runSubmittersGrants.Has(address(a)) {
229 panic( "cannot remove " + a + ": not granted by " + runSubmittersMgr +
230 ", only GovDAO may remove it" )
231 }
232 }
233
234 // The delegate may not empty the list, whatever it granted.
235 //
236 // An empty run_submitters means the gate is OFF -- anyone on the chain may
237 // send MsgRun. So emptying it is not a smaller version of removing one
238 // address, it is the opposite of what the delegation is for: it would let a
239 // delegate authorized to curate a list unilaterally revoke the whole
240 // restriction GovDAO voted for.
241 //
242 // Grant-scoping alone does not prevent this. It holds only while at least
243 // one entry the delegate did not grant survives, and GovDAO replacing the
244 // list wholesale can remove its own entries without touching the grant
245 // record. A floor makes the invariant structural instead of emergent.
246 //
247 // Counted against the parameter, not the argument: the caller may name
248 // addresses that are not listed, or name one twice, and neither shrinks the
249 // list. Only GovDAO can go to zero, through the whole-list setter, where the
250 // resulting list is on the ballot.
251 removing := make( map [ string ] bool , len(addrs))
252 for _, a := range addrs {
253 removing[a] = true
254 }
255 remaining := 0
256 for _, a := range GetRunSubmitters() {
257 if !removing[a] {
258 remaining++
259 }
260 }
261 if remaining == 0 {
262 panic( "refusing to empty the " + runSubmittersKey +
263 " allowlist: an empty list disables the gate entirely, so only " +
264 "GovDAO may do it" )
265 }
266
267 prms.UpdateSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey, addrs, false )
268 for _, a := range addrs {
269 runSubmittersGrants.Remove(address(a))
270 }
271 chain.Emit(DelegateWriteEvent,
272 "key" , runSubmittersKey, "realm" , runSubmittersMgr, "op" , "remove" )
273 }
274
275 // GetRunSubmitters returns the current allowlist.
276 func GetRunSubmitters() [] string {
277 vals, _ := prms.GetSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey)
278 return vals
279 }
280
281 // ProposeSetRunSubmitters creates a GovDAO proposal replacing the whole
282 // run_submitters allowlist.
283 //
284 // This is the only way to set the list by vote: the generic factories refuse
285 // the key (see assertNotRunSubmittersKey), so every whole-list write comes
286 // through here and carries the rule below.
287 //
288 // The proposer must be on the list they propose.
289 //
290 // An empty run_submitters means the gate is off and anyone may MsgRun. A
291 // non-empty one therefore decides who may run code at all -- and a list naming
292 // nobody who can create a GovDAO proposal cannot be undone, because creating a
293 // proposal needs MsgRun: a ProposalRequest carries an Executor, and MsgCall
294 // cannot build one from string arguments. The vote would end governance.
295 //
296 // Requiring the proposer's own address is a cheap way to prove the list is
297 // usable rather than merely plausible. GovDAO refuses a proposal from a
298 // non-member (PreCreateProposal, "only members can create new proposals"), so
299 // if this proposal exists at all its author is a member -- and they just signed
300 // the transaction that created it, so the address demonstrably holds a key. A
301 // list that merely NAMES a member proves neither: the address may belong to
302 // nobody, since any member can enroll an arbitrary address.
303 //
304 // Checked here, at proposal creation, rather than inside the executor. Nothing
305 // in r/gov/dao recovers from an executor panic, so a check that fires at
306 // execution turns a passed proposal into one that can never be executed. Here
307 // the refusal reaches a person who can still fix the list and propose again.
308 //
309 // This is a floor, not an invariant. The proposer may resign from GovDAO later,
310 // and the list is not re-checked when they do. It rules out arriving at a dead
311 // list in one vote; it cannot rule out drifting into one.
312 func ProposeSetRunSubmitters(cur realm, addrs [] string ) dao.ProposalRequest {
313 // IsCurrent before Previous, as AGENTS.md requires and assertDelegate does.
314 //
315 // Belt and braces here rather than load-bearing: the compiler already
316 // refuses anything but `cur` or `cross(rlm)` as the first argument to a
317 // crossing function, so this cur is live by construction and the check
318 // cannot fire. It earns its place if this ever becomes a helper taking an
319 // ordinary realm parameter, which is the shape assertDelegate has and where
320 // a stashed value really can be threaded in.
321 if !cur.IsCurrent() {
322 panic( "unauthorized: cur is not the caller's live realm" )
323 }
324 proposer := cur.Previous().Address()
325 listed := false
326 for _, a := range addrs {
327 if a == proposer.String() {
328 listed = true
329 break
330 }
331 }
332 if len(addrs) > 0 && !listed {
333 panic( "refusing to propose a " + runSubmittersKey + " allowlist that omits " +
334 "the proposer " + proposer.String() + ": a non-empty list that names nobody " +
335 "who can create a proposal cannot be changed back" )
336 }
337
338 title := "Set the " + runSubmittersKey + " allowlist"
339 desc := "Replace the " + runSubmittersKey + " allowlist, which gates who may " +
340 "send MsgRun."
341 if len(addrs) == 0 {
342 desc += " This empties the list, which switches the gate OFF: anyone may " +
343 "send MsgRun."
344 } else {
345 desc += " Only these addresses may send MsgRun. Creating a GovDAO proposal " +
346 "needs MsgRun, so this list also decides who can govern."
347 for _, a := range addrs {
348 desc += "\n- " + a
349 }
350 }
351
352 callback := func (cur realm) error {
353 setRunSubmitters(addrs)
354 chain.Emit( "SetRunSubmitters" , "key" , runSubmittersKey, "proposer" , proposer.String())
355 return nil
356 }
357 return dao.NewProposalRequest(title, desc,
358 dao.NewSimpleExecutor( 0 , cur, callback, "" ))
359 }
360
361 // setRunSubmitters replaces the whole allowlist.
362 //
363 // SetSysParamStrings, not UpdateSysParamStrings: Update with add=true appends
364 // non-duplicates onto what is already there, so it can never remove an address
365 // or empty the list. This realm reserves the key from the generic factories, so
366 // this is the only route by vote -- if it appended, the parameter would be
367 // append-only chain-wide, a compromised address could never be de-listed, and
368 // the gate could never be turned back off.
369 //
370 // A named function rather than the executor's body inline, so a test can reach
371 // it: a closure held in a ProposalRequest cannot be called from outside.
372 func setRunSubmitters(addrs [] string ) {
373 prms.SetSysParamStrings(vmModulePrefix, vmParamsSubmodule, runSubmittersKey, addrs)
374 }
375
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.