gno.land/p/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/governor/v0⧉ Package Open in gnoweb ↗
Kind Pure package
Name v0
Namespace g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr / governor
Exported functions n/a — not supported for pure packages by the node (vm/qfuncs)
Module gno.land/p/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/governor/v0
gno 0.9 governor.gno gno
⧉
1 // Package governor is a proposal engine: it decides what a body of holders has 2 // agreed to, and it decides nothing about what agreement means. 3 // 4 // THE SPLIT IS THE POINT. A Governor holds proposals, epochs, quorum floors, the 5 // roll of eligible voters and the tally; it does not hold a treasury, a token, or 6 // any idea of what "adopt this" should DO. Adoption produces a KIND and a 7 not supported for pure packages by the node (vm/qfuncs)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
// PAYLOAD, both strings, and the realm that installed the governor is what turns
8 // those into an effect. r/govern is the worked consumer, and r/offerer is the
9 // fixture proving a SECOND realm can extend the same engine from a different
10 // package without being trusted by it.
11 //
12 // Voting power is read from a snapshot, never from a live balance. The electorate
13 // is asked what an address COULD vote at the block a proposal opened, which is
14 // what stops a vote being bought after the question is known -- see
15 // p/kourt/grc20votes, which remembers exactly that, and p/kourt/checkpoint under
16 // it. A bar is frozen for the life of a proposal for the same reason: a live
17 // numerator against a moving denominator produced turnout above 100% of its own
18 // bar and a permissionless verdict flip. VOTELOCK.md is the argument.
19 //
20 // Strings rather than structs throughout the proposal surface, and that is forced
21 // rather than chosen: MsgCall.Args is []string, so a struct payload cannot cross
22 // the transaction boundary at all. The comment on the payload type below records
23 // what that costs and why nothing better is available.
24 package governor
25
26 import (
27 "chain"
28 "crypto/sha256"
29 "strconv"
30 "strings"
31
32 bptree "gno.land/p/nt/bptree/v0"
33 ufmt "gno.land/p/nt/ufmt/v0"
34 )
35
36 // A proposal is a KIND and a PAYLOAD, both strings.
37 //
38 // Forced, and better than the alternative. MsgCall.Args is []string
39 // (gno.land/pkg/sdk/vm/msgs.go) and convertArgToGno switches on
40 // gno.BaseOf(argT), so scalars and named scalars like `address` convert; a
41 // struct, interface, pointer or func panics with "unexpected type in contract
42 // arg". An entrypoint taking one is uncallable by transaction.
43 //
44 // A []byte converts too, base64-decoded, so the string is a choice: a payload
45 // arrives as text a voter can read, where bytes would arrive as base64.
46 //
47 // A proposer cannot supply a closure either. A realm can persist one, but
48 // MsgRun forces the ephemeral package private and the save walk refuses it
49 // (realm.go, "cannot persist function or method from the private realm").
50 //
51 // The property that falls out is what OpenZeppelin cannot offer: Describe and
52 // Do consume the identical string, so what voters read IS what runs.
53 type Kind interface {
54 // Name is the registry key.
55 Name() string
56
57 // Describe renders a payload for the people voting on it.
58 //
59 // It must be a total function of the payload ALONE. Read live state here
60 // and two voters at different heights are deciding different questions
61 // while looking at the same page.
62 Describe(payload string ) string
63
64 // Check validates a payload against the world as it is now. Called when
65 // the proposal opens AND again immediately before Do.
66 //
67 // The second call is the point. A proposal that was sound a week ago need
68 // not be sound now, and the alternative is discovering that inside Do,
69 // half-applied.
70 Check(payload string ) error
71
72 // Do performs it. Non-crossing, taking the realm as data, so a /p/ package
73 // could implement this unchanged — crossing functions are illegal outside
74 // a realm.
75 //
76 // Returning an error and panicking are different decisions. An error
77 // FINISHES the proposal: recorded failed, slot returned, no retry. Say so
78 // when the answer will not change.
79 //
80 // A panic ABORTS the transaction, writing nothing — including the failure
81 // — so the proposal stays Succeeded and anybody may try again. Say so when
82 // a retry could work, or when half-finishing is worse than not starting:
83 // an abort is the only rollback gno offers. govern:batch depends on it.
84 Do(_ int , rlm realm, payload string ) error
85 }
86
87 // Rules are per kind, never global. Renaming a parameter and spending the
88 // reserve should not need the same turnout, which is why one global quorum is
89 // always wrong for something.
90 //
91 // Basis points as int64, never float64: gno's own govdao compares against a
92 // float 66.66, and a threshold decided by binary floating point is a rounding
93 // argument waiting to happen. yes*bps >= (yes+no)*ThresholdBps is exact.
94 //
95 // bps is named once because it is load-bearing in three places at once: the
96 // tally, the rendered percentages and the supply ceiling. A scale written out
97 // separately in each is a number that can come to disagree with itself.
98 const bps = int64 ( 10000 )
99
100 // maxWeighable is the largest snapshot supply a tally can weigh without yes*bps
101 // overflowing int64 (and going negative, which reads a won vote as lost). The
102 // Electorate contract requires PastTotal to stay within it; grc20votes caps its
103 // own supply here exactly, so for that token this ceiling is never approached. It
104 // is checked at the Propose door for a swapped-in electorate that does not.
105 const maxWeighable = int64 ( 9223372036854775807 ) / bps
106
107 type Rules struct {
108 QuorumBps int64 // of the snapshot supply, counting abstain as turnout
109
110 // ThresholdBps is the share of yes+no (abstain excluded) that yes must reach.
111 // The comparison is INCLUSIVE — yes*bps >= (yes+no)*ThresholdBps — so at 5000
112 // a 50-50 tie PASSES. A realm wanting a strict majority sets 5001 or more; the
113 // bootstrap and gno's govdao use a 6600 supermajority.
114 ThresholdBps int64
115
116 // ProposeBps is what a proposer must hold, as a fraction of the snapshot
117 // supply, to open a question at all.
118 //
119 // Attention control, not spam control: opening a proposal already costs a
120 // new key's worth of deposit. The list is what holders read, and a list
121 // nobody reads is a governor nobody governs. Zero means anyone may
122 // propose — right for a small realm, wrong for a public token.
123 ProposeBps int64
124 VotingBlocks int64
125 DelayBlocks int64 // succeeded -> executable; the timelock
126 GraceBlocks int64 // executable -> expired
127 }
128
129 const (
130 stateActive int8 = iota
131 stateDefeated
132 stateSucceeded
133 stateExecuted
134 stateFailed
135 stateCanceled
136 stateExpired
137 )
138
139 // proposal is one question, frozen.
140 type proposal struct {
141 id int64
142 kind string
143 payload string
144 title string
145 proposer address
146
147 // Both copied when the proposal opens: the rules, so a later change cannot
148 // move the bar earlier voters faced; the snapshot epoch, so the electorate
149 // is the one that existed when the question was asked.
150 rules Rules
151 epoch uint32
152 total int64 // supply at that epoch, so quorum has a fixed denominator
153
154 // engaged is the quorum's DENOMINATOR; total is everything in issue. Equal
155 // for this realm's token and not necessarily for another: an electorate may
156 // drop idle weight out of the bar while that weight can still vote. So
157 // quorum divides by engaged, and "what could still be cast" divides by
158 // total.
159 engaged int64
160
161 // quorumFloor, when positive, is an ABSOLUTE turnout the proposal needs
162 // instead of the rules' QuorumBps fraction of engaged — a figure the consumer
163 // computed itself (a court's max(5% supply, min(1×X̄, ⅓ votable))) and passed
164 // through ProposeWithQuorum. Snapshotted here like every other bar, so it
165 // cannot move under an open vote. Zero means the bps formula applies, which is
166 // every ordinary caller.
167 quorumFloor int64
168
169 opened int64
170 closes int64
171 // The same two moments in unix seconds, written beside the heights and never
172 // derived afterwards. A VOTING WINDOW IS A PROMISE TO A VOTER, and a height
173 // only means a date if the chain's pace never changes; a proposal opened
174 // before these existed carries 0 and keeps the height it was opened under.
175 openedTime int64
176 closesTime int64
177 ready int64 // set when it succeeds
178 readyTime int64 // its wall-clock twin, written at the same moment
179 state int8
180 reason string
181
182 yes, no, abstain int64
183 // voted exists only to refuse a second vote from the same address. Nothing
184 // renders it and nothing else reads it.
185 //
186 // A tree per proposal rather than one shared tree keyed by (id, voter),
187 // for cleanup rather than cost: this buys a whole node on the first vote
188 // (~4,500 bytes) where a shared tree would cost one entry, but dropping it
189 // is one assignment and the deposit goes back to whoever settles. Clearing
190 // a range out of a shared tree is a removal per voter, unbounded, and
191 // bptree forbids removing during iteration.
192 //
193 // Dropped the moment the proposal closes: Vote refuses a closed proposal
194 // before it looks here, so the tree would be rent on something nobody can
195 // consult. The roll survives in the Voted events.
196 voted *bptree.BPTree
197 }
198
199 // entry is a kind and whether it may actually be proposed. Separate, because
200 // publishing code is something a realm does and letting that code hold this
201 // governor's authority is something the holders do. One registration call fuses
202 // them, and then whoever may register can grant themselves any power with no
203 // vote.
204 type entry struct {
205 kind Kind
206 rules Rules
207 live bool
208 }
209
210 // maxLive bounds how many questions can be open at once. A security parameter,
211 // not a storage one: it is the only thing between a determined proposer and a
212 // list nobody can read.
213 //
214 // Cancel frees a slot outright. Time only makes one RECLAIMABLE — a read can
215 // work out that a proposal is over but cannot write it down — so the slot
216 // comes back when somebody calls Settle, or when sweep finds it on the next
217 // Propose. Reading this as "time frees a slot" is how the reclaim came to be
218 // something a proposer could hold shut; see the cursor sweep keeps.
219 //
220 // The open section of the front page has no length cap of its own, so its
221 // worst case is this many rows at maxTitle and maxKindName each: 14,220 bytes,
222 // which is a page. Ten thousand slots would be 2.2MB, which is not. A test
223 // holds the product against a ceiling, so raising either number fails it.
224 const maxLive = 64
225
226 // govLanes is how many of those slots only the governor's own kinds may take.
227 //
228 // Without it the sixty-four are a commons and any one kind may hold all of
229 // them. Sixty-four proposals of some cheap kind with a long voting window, and
230 // nothing else can be asked until that window runs out — including the
231 // govern:rules vote that would shorten the window. The realm stays up and stops
232 // being governable, which is the one failure it must not have, because every
233 // other kind of congestion is something the holders can vote their way out of.
234 //
235 // Priced rather than assumed: the bootstrap terms put ProposeBps at zero, so
236 // any holder may propose, and sixty-four proposals is about 56 GNOT of deposit
237 // — refundable, and held for one bootstrap voting window of a week.
238 //
239 // Eight rather than one. The governor has five kinds of its own and they are
240 // not alternatives: retuning a kind, adopting another, and retiring a third can
241 // all be live questions at once, and a lane of one would mean the first of them
242 // blocks the rest. Eight leaves room for a second round of each.
243 //
244 // This does NOT stop the governor's own kinds crowding each other out, and it
245 // is not meant to. A realm whose holders have filled the governance lane with
246 // governance is congested by its own doing and can wait; a realm shut out of
247 // governance by a kind it adopted for something else cannot.
248 const govLanes = 8
249
250 // maxOpen is what any other kind may fill, which is the number a proposer
251 // actually meets. maxLive stays the bound on the front page, since the lane is
252 // a reallocation of the sixty-four and not an addition to them.
253 const maxOpen = maxLive - govLanes
254
255 var (
256 errNoSuchKind = govErr( "no kind by that name has been offered" )
257 errAlreadyLive = govErr( "that kind is already adopted" )
258 errNotLive = govErr( "that kind is not adopted" )
259 errCannotRetireBuiltin = govErr( "the governor's own kinds cannot be retired" )
260 )
261
262 // Governor is one governing body: which powers it has adopted, every question
263 // it has been asked, and where its slot reclaim had got to.
264 //
265 // Allocated by the consuming realm, which is what makes this a library rather
266 // than a realm. A /p/ package's own state is frozen after init, so nothing
267 // durable could live at package level; the trees below carry the consumer's
268 // storage stamp, and every method here borrows the consumer's authority back
269 // for the write.
270 //
271 // Every field is unexported and no method hands one out. /p/-declared types can
272 // be named by other /p/ packages, so an exported *proposal or a method
273 // returning one would let a stranger declare a mutator over it — and the
274 // storage-realm borrow would run that mutator under the CONSUMING realm's
275 // authority. See gno-security-guide.md §3(B) and §4.
276 type Governor struct {
277 voters Electorate
278 token Token
279
280 kinds *bptree.BPTree // name -> *entry
281 proposals *bptree.BPTree // enc(id) -> *proposal
282 openIdx *bptree.BPTree // digest -> enc(id), the live ones
283
284 propSeq int64
285
286 // executing is the re-entrancy latch, and it is held for the WHOLE of every
287 // state-changing call — propose, castVote and Execute — not only Execute.
288 //
289 // It started as an Execute-only flag, against a kind calling back in while
290 // its own Do runs and seeing a proposal mid-execution. The other two read it
291 // and never wrote it, which made those reads decorative: propose and castVote
292 // both call into the consumer-supplied Electorate (PastVotes, PastTotal,
293 // EngagedTotal, and Height/Now by way of settle) and both mutate afterwards,
294 // so a hostile or merely re-entrant electorate could nest a second call
295 // inside the first. castVote was the sharp one — its already-voted guard
296 // reads state that the same function does not write until after the external
297 // call, so a nested frame passed both checks and the tally took the weight
298 // twice.
299 //
300 // The name is kept because every panic and every test says it; what changed
301 // is the span, from one call to three.
302 //
303 // THE THREE PANICS GAINED A CLAUSE RATHER THAN NEW WORDING. Each said "from
304 // inside an execution", which stopped being the only cause the moment vote
305 // and propose began holding the latch too. Rewriting them outright broke
306 // TestExecutionCannotReEnterTheGovernor, which pins each message by
307 // substring — correctly, because a panic string is API for anyone matching on
308 // it. Appending "or a vote" makes them true without moving what was pinned.
309 executing bool
310
311 // sweepFrom is where the next slot reclaim starts scanning, and it rotates.
312 // The index is keyed by digest(kind, payload) and a proposer chooses their
313 // payload, so they choose their key: park a few long-running proposals on
314 // the lowest keys and a FIXED window never sees past them, freeing nothing
315 // while the rest of the list is finished business. Beating the lowest of
316 // fifty-six random keys is a few hundred hashes offline.
317 sweepFrom string
318 }
319
320 // reserved names the governor's own kinds. Offer refuses this prefix, so no
321 // realm can publish something that renders as a built-in.
322 const reserved = "govern:"
323
324 // isReserved reports whether a name belongs to the governor itself.
325 //
326 // One predicate, two callers: Offer refusing to publish under the prefix, and
327 // govern:retire refusing to withdraw something carrying it. It was written
328 // twice, and a weakened retire copy would let the holders withdraw
329 // govern:adopt — after which no kind could ever be adopted again and no
330 // proposal could restore it.
331 func isReserved(name string ) bool {
332 return len(name) >= len(reserved) && name[:len(reserved)] == reserved
333 }
334
335 // NewRules builds the terms a kind would pass on. A realm cannot allocate
336 // another realm's struct — govern.Rules{...} written elsewhere is refused with
337 // "cannot allocate ... in realm" — so the value comes into existence here and
338 // the caller passes numbers. Without it, Offer was uncallable from outside this
339 // package.
340 func NewRules(quorumBps, thresholdBps, votingBlocks, delayBlocks, graceBlocks, proposeBps int64 ) Rules {
341 return Rules{
342 QuorumBps: quorumBps, ThresholdBps: thresholdBps,
343 VotingBlocks: votingBlocks, DelayBlocks: delayBlocks,
344 GraceBlocks: graceBlocks, ProposeBps: proposeBps,
345 }
346 }
347
348 // Offer shelves a kind. It takes effect on nothing.
349 //
350 // Ungated, because an offered kind can do nothing. What it buys is a name
351 // pointing at published, immutable code the holders are later asked to approve.
352 //
353 // The two-step exists because MsgCall.Args is []string: an account cannot hand
354 // over a Kind, only a realm can construct one. If adopting required passing
355 // the value, only realms could grow the governor — and that decision has to
356 // belong to the holders. Offer is the realm's half; the vote is theirs.
357 func (g *Governor) Offer(who address, k Kind, r Rules) {
358 name := k.Name()
359 mustBeUsableName(name)
360 if isReserved(name) {
361 panic( "govern: that prefix belongs to the governor" )
362 }
363 g.mustBeSaneRules(r)
364
365 // A name, once offered, is bound to that code forever.
366 //
367 // The alternative — allowing a re-offer while the kind is not yet adopted —
368 // is a live attack, not an untidiness. Offer harmless code, wait for an
369 // adoption proposal to open on it, then re-offer under the same name while
370 // the vote runs. The holders read the first Describe, approve it, and Do
371 // dispatches to the second. gno's own daokit has this shape and records it
372 // as a TODO; here the vote would be a signature on a blank cheque.
373 //
374 // Pinning the value into the proposal would also close it, at the cost of
375 // a Kind stored per proposal and a rule about which copy wins. Refusing
376 // the rewrite is smaller and matches how the chain already behaves: a
377 // realm cannot be redeployed at its path either. A realm with new code
378 // publishes a new name — treasury/spend/v2 — which the holders then have
379 // to adopt knowingly, which is the entire point.
380 if g.entryOf(name) != nil {
381 panic( "govern: that name is taken; publish a new one rather than " +
382 "rewriting what may already be under a vote" )
383 }
384 g.kinds.Set(name, &entry{kind: k, rules: r})
385
386 // The terms go out with it. A holder deciding whether to adopt this is
387 // deciding on the code AND on the bar it would pass at, and the two are
388 // bound together from here — Offer refuses to rewrite either.
389 chain.Emit(kindOfferedEvent,
390 "name" , name,
391 "offerer" , who.String(),
392 "quorumBps" , strconv.FormatInt(r.QuorumBps, 10 ),
393 "thresholdBps" , strconv.FormatInt(r.ThresholdBps, 10 ),
394 "votingBlocks" , strconv.FormatInt(r.VotingBlocks, 10 ),
395 "delayBlocks" , strconv.FormatInt(r.DelayBlocks, 10 ),
396 "graceBlocks" , strconv.FormatInt(r.GraceBlocks, 10 ),
397 "proposeBps" , strconv.FormatInt(r.ProposeBps, 10 ),
398 )
399 }
400
401 // maxTitle bounds the headline, which goes on the shared page. Every open
402 // proposal's title is concatenated into Render(""), and the only cap there is
403 // on the decided tail — so the page every holder reads is as long as the open
404 // titles put together. Anything needing more room is what the payload is for.
405 const maxTitle = 120
406
407 // maxPayload bounds the argument. Looser than the title on purpose: this is
408 // what the proposal actually says, a batch carries up to maxBatch members in
409 // one, and the cost falls on the proposer — Render lists titles, so nobody
410 // loads a payload without asking for that proposal by number.
411 const maxPayload = 4096
412
413 // maxKindName bounds a name. Offer is ungated, so it is chosen by whoever
414 // turns up, and everything that renders a proposal renders it.
415 const maxKindName = 64
416
417 // mustBeUsableName refuses a name that cannot be used everywhere a name is
418 // used.
419 //
420 // A kind's name is not only a registry key. It is the first field of a batch
421 // member line, and parseBatch ends that field at the first SPACE; it goes into
422 // the digest that decides whether two proposals are the same question; and it
423 // is rendered on a page somebody has to read before voting. A name that works
424 // in one of those and not the others is a kind that is quietly unusable.
425 //
426 // A space is the one that looks harmless and is not. "my kind" offers and
427 // proposes perfectly well, and then a batch reads everything after the space
428 // as the payload — so the member resolves to a kind called "my", the batch is
429 // refused with "no kind called my", and nothing points at the name.
430 //
431 // Printable ASCII, no space. That is what every kind here already uses — fee,
432 // treasury/spend, govern:adopt — and it is the largest set that works in all
433 // three places at once.
434 func mustBeUsableName(name string ) {
435 if name == "" {
436 panic( "govern: a kind needs a name" )
437 }
438 if len(name) > maxKindName {
439 panic( "govern: that name is too long" )
440 }
441 for i := 0 ; i < len(name); i++ {
442 if c := name[i]; c <= ' ' || c > '~' {
443 panic( "govern: a kind name must be printable ASCII with no spaces" )
444 }
445 }
446 }
447
448 // mustBeUsableTitle bounds the one piece of free prose an ungated caller puts
449 // on a page everybody reads.
450 //
451 // A title goes onto the front page inside a markdown list item, so a newline
452 // ends that item and starts a row of the proposer's own — a forged proposal
453 // with a state and a link they choose:
454 //
455 // - [#1](:1) **x
456 // - [#999](:999) **Ratified by the foundation** — `govern:minter` · succeeded
457 //
458 // The bootstrap kinds ask for no stake, so that cost nothing but a transaction.
459 //
460 // Control characters, not printable-ASCII: a title is prose and keeps spaces
461 // and any language. Markdown within a line is left alone — it cannot leave the
462 // row it belongs to, and structure is the part a reader cannot check.
463 func mustBeUsableTitle(title string ) {
464 if len(title) > maxTitle {
465 panic( "govern: that title is too long" )
466 }
467 if err := TextOnly(title); err != nil {
468 panic( "govern: a title " + err.Error())
469 }
470 }
471
472 // TextOnly reports whether a string is text rather than page structure.
473 //
474 // Exported because it is a kind author's problem. A payload comes from whoever
475 // proposes — any address at all when ProposeBps is zero — and Describe renders
476 // it onto a page others vote from, so a raw payload hands page structure to a
477 // stranger. The built-ins are safe by accident: their payloads are kind names
478 // and addresses, which cannot hold a newline. It bites the first kind that
479 // takes free text.
480 //
481 // func (k mykind) Check(payload string) error {
482 // if len(payload) > 280 {
483 // return errTooLong
484 // }
485 // return govern.TextOnly(payload)
486 // }
487 //
488 // Control characters, not printable-ASCII, so prose in any language survives —
489 // every byte of a multi-byte character is >= 0x80. Markdown within a line is
490 // allowed: it cannot leave the row it belongs to.
491 //
492 // The governor cannot apply this to payloads itself; govern:batch is defined
493 // in terms of newlines. Only a kind knows its own grammar.
494 func TextOnly(s string ) error {
495 for i := 0 ; i < len(s); i++ {
496 if c := s[i]; c < 0x20 || c == 0x7f {
497 return errNotTextOnly
498 }
499 }
500 return nil
501 }
502
503 var errNotTextOnly = govErr( "cannot contain control characters" )
504
505 // saneRules is the one definition of terms this realm will accept — one
506 // function, two callers, after two copies drifted apart.
507 //
508 // Both copies missed ProposeBps. Above a hundred percent it asks a proposer to
509 // hold more than everything, so nobody can open a question under that kind:
510 // adopted and unusable. And govern:rules accepted only five of the six terms,
511 // so the one field where a bad value bricks a kind was the one nobody could
512 // retune. It takes all six now.
513 func saneRules(r Rules) error {
514 if r.QuorumBps < 0 || r.QuorumBps > bps || r.ThresholdBps < 0 || r.ThresholdBps > bps {
515 return govErr( "basis points out of range" )
516 }
517 if r.ProposeBps < 0 || r.ProposeBps > bps {
518 // Exactly bps is allowed: "only somebody holding the entire supply may
519 // propose this" is a coherent, if severe, thing to want.
520 return govErr( "a proposer cannot be asked to hold more than the whole supply" )
521 }
522 if r.VotingBlocks <= 0 {
523 return govErr( "a vote needs a period" )
524 }
525 if r.GraceBlocks <= 0 {
526 // A decision has to be able to expire, because never expiring leaks a
527 // slot.
528 //
529 // A Succeeded proposal keeps its place in the open index until it is
530 // executed, because until then a duplicate would be a second live copy
531 // of the same question. With no grace period there is nothing that ever
532 // ends it: Cancel refuses a decided proposal, Settle and sweep find
533 // nothing to change, and the slot is held for the life of the realm.
534 // Sixty-four like that and nobody can open a proposal again.
535 //
536 // Refusing zero is what makes "every proposal eventually gives its slot
537 // back" true by construction, and it agrees with what this realm
538 // already says about grace: a decision nobody executed is a decision
539 // about a world that has moved.
540 return govErr( "a decision has to expire; a grace period of zero holds " +
541 "its slot for the life of the realm" )
542 }
543 if r.DelayBlocks < 0 {
544 // GraceBlocks < 0 needs no test here: the GraceBlocks <= 0 return above
545 // already refuses it. Negative behaves as zero everywhere it is used, so
546 // this refuses a value that means nothing rather than one that does harm —
547 // and stops it being stored and rendered as a term somebody agreed to.
548 return govErr( "a period cannot be negative" )
549 }
550 return nil
551 }
552
553 func (g *Governor) mustBeSaneRules(r Rules) {
554 if err := saneRules(r); err != nil {
555 panic( "govern: " + err.Error())
556 }
557 }
558
559 // init makes the governor able to govern itself, and nothing else. These are
560 // live from the first block because adopting a kind is itself a kind, so there
561 // is nothing to bootstrap them with. Everything beyond must be offered and
562 // adopted, so not even the deployer can add a power without a vote.
563
564 // BootstrapRules are the terms the governor governs ITSELF on.
565 //
566 // Exported because a consuming realm registering a power of its own —
567 // the mint, most of all — should govern it on the same terms rather than
568 // inventing a second set that can drift from these.
569 func BootstrapRules() Rules {
570 return Rules{
571 // A fifth of the supply has to turn out. Low enough to reach with
572 // dispersed holders, high enough that a handful cannot decide alone
573 // while everybody else is asleep.
574 QuorumBps: 2000 ,
575
576 // Two thirds of what was cast, not half. A simple majority is the
577 // wrong bar for changing the rules of the game — these kinds decide
578 // who may mint and what powers exist — and 6600 is exact where gno's
579 // own govdao carries 66.66 as a float64 and compares with >=.
580 ThresholdBps: 6600 ,
581
582 // A week to vote, so holders in any timezone get a turn. It is a
583 // DEADLINE and not a duration: a vote nothing further could change
584 // closes on the arithmetic, so an agreed decision does not wait it out.
585 VotingBlocks: 7 * 24 * 60 * 60 / 5 ,
586
587 // Two days between deciding and doing, which is what a timelock is
588 // for: time for somebody who dislikes the outcome to sell, leave, or
589 // argue before it takes effect.
590 DelayBlocks: 2 * 24 * 60 * 60 / 5 ,
591
592 // A fortnight to execute, and then the decision expires. A proposal
593 // nobody executed for two weeks is a decision about a world that has
594 // moved, and gno's own govdao had to bolt on a reject path precisely
595 // because proposals could otherwise sit forever.
596 GraceBlocks: 14 * 24 * 60 * 60 / 5 ,
597 }
598 }
599
600 // installBuiltins registers the kinds the governor governs itself with.
601 func (g *Governor) installBuiltins() {
602 // The terms the governor governs ITSELF on. See BootstrapRules.
603 bootstrap := BootstrapRules()
604 g.kinds.Set(adoptKind{}.Name(), &entry{kind: adoptKind{}, rules: bootstrap, live: true })
605 g.kinds.Set(retireKind{}.Name(), &entry{kind: retireKind{}, rules: bootstrap, live: true })
606 g.kinds.Set(rulesKind{}.Name(), &entry{kind: rulesKind{}, rules: bootstrap, live: true })
607 g.kinds.Set(batchKind{}.Name(), &entry{kind: batchKind{}, rules: bootstrap, live: true })
608 }
609
610 // adoptKind turns an offered kind live. Its payload is only a name, so what
611 // the holders read is exactly what they are approving.
612 type adoptKind struct {}
613
614 func (k adoptKind) Name() string { return reserved + "adopt" }
615
616 func (k adoptKind) describe(g *Governor, payload string ) string {
617 out := "adopt the kind `" + payload + "`, letting it be proposed and executed"
618 e := g.entryOf(payload)
619 if e == nil {
620 return out + "\n\n_Nothing has been offered under that name._"
621 }
622 // The terms, not just the name: a holder approving a power needs to see how
623 // easily it can be used. A hostile realm offers a spending kind with a
624 // hundredth of a percent quorum and a one-block vote, and the adoption
625 // reads exactly like an honest one.
626 //
627 // Still a total function of the payload — an offered name is bound to its
628 // code permanently, and govern:rules refuses to retune an unadopted kind,
629 // so none of this can move between proposing and executing.
630 out += ufmt.Sprintf( "\n\nIt would pass on:" +
631 "\n- quorum: %s of the supply" +
632 "\n- threshold: %s of votes cast" +
633 "\n- time to vote: %s" +
634 "\n- delay before it can run: %s" +
635 "\n- expires after: %s" ,
636 pct(e.rules.QuorumBps), pct(e.rules.ThresholdBps),
637 span(e.rules.VotingBlocks), span(e.rules.DelayBlocks), span(e.rules.GraceBlocks))
638 // Always rendered, including when it is zero — which is when it matters
639 // most. Zero means Propose skips the stake check, so any address may open
640 // a proposal holding nothing, and omitting the line read as "not
641 // applicable" rather than "anybody". A permissive default is the term a
642 // page most needs to say out loud.
643 out += "\n- who may propose: " + proposerBar(e.rules.ProposeBps)
644 return out
645 }
646
647 func (k adoptKind) check(g *Governor, payload string ) error {
648 e := g.entryOf(payload)
649 if e == nil {
650 return errNoSuchKind
651 }
652 if e.live {
653 return errAlreadyLive
654 }
655 return nil
656 }
657
658 func (k adoptKind) run(g *Governor, dispatch Dispatch, payload string ) error {
659 e := g.entryOf(payload)
660 if e == nil {
661 return errNoSuchKind
662 }
663 e.live = true
664 return nil
665 }
666
667 // retireKind withdraws one. Offered-but-not-live is the resting state, so a
668 // retired kind can be adopted again without the realm re-offering it.
669 type retireKind struct {}
670
671 func (k retireKind) Name() string { return reserved + "retire" }
672
673 func (k retireKind) describe(g *Governor, payload string ) string {
674 return "retire the kind `" + payload + "`, so it can no longer be proposed" +
675 "\n\nThis also kills any proposal of that kind that has already been " +
676 "decided and not yet run: Execute refuses a kind that is no longer " +
677 "adopted, and records the proposal as failed. Retiring is not only " +
678 "about the future."
679 }
680
681 func (k retireKind) check(g *Governor, payload string ) error {
682 e := g.entryOf(payload)
683 if e == nil {
684 return errNoSuchKind
685 }
686 if !e.live {
687 return errNotLive
688 }
689 if isReserved(payload) {
690 return errCannotRetireBuiltin
691 }
692 return nil
693 }
694
695 func (k retireKind) run(g *Governor, dispatch Dispatch, payload string ) error {
696 e := g.entryOf(payload)
697 if e == nil {
698 return errNoSuchKind
699 }
700 e.live = false
701 return nil
702 }
703
704 // Propose opens a question. One maketx call, two strings — which is the whole
705 // design and the reason the payload is not a struct.
706 //
707 // The quorum is the rules' QuorumBps fraction of the engaged weight. A consumer
708 // that computes its own absolute bar — a court sizing quorum to a claim's open
709 // interest — uses ProposeWithQuorum instead.
710 func (g *Governor) Propose(who address, kind, payload, title string ) int64 {
711 return g.propose(who, kind, payload, title, 0 )
712 }
713
714 // ProposeWithQuorum is Propose with an ABSOLUTE turnout the question needs,
715 // replacing the rules' QuorumBps fraction for this one proposal. The figure is
716 // the consumer's to compute (a court's max(5% supply, min(1×X̄, ⅓ votable))) and
717 // is snapshotted here, so the bar cannot move under an open vote. A non-positive
718 // floor is refused — a caller wanting the bps formula calls Propose.
719 //
720 // Additive: Propose is unchanged and passes zero, which every existing user does.
721 func (g *Governor) ProposeWithQuorum(who address, kind, payload, title string , quorumFloor int64 ) int64 {
722 if quorumFloor <= 0 {
723 panic( "govern: a quorum floor has to be positive; use Propose for the bps bar" )
724 }
725 return g.propose(who, kind, payload, title, quorumFloor)
726 }
727
728 func (g *Governor) propose(who address, kind, payload, title string , quorumFloor int64 ) int64 {
729 if g.executing {
730 panic( "govern: cannot propose from inside an execution or a vote" )
731 }
732 // Held for the call, for the reason castVote's own note gives at length: this
733 // function reads the consumer-supplied Electorate four times (Epoch,
734 // PastTotal, EngagedTotal, PastVotes) and writes a proposal afterwards, so
735 // the read-only check above was excluding nothing on its own.
736 //
737 // Its exposure is milder than castVote's — there is no
738 // check-then-write-the-thing-you-checked pair here, so a nested frame would
739 // mint a second proposal rather than double-count a tally — but two
740 // proposals from one call is still a write the caller did not ask for, and
741 // the latch is the same two lines.
742 g.executing = true
743 defer func () { g.executing = false }()
744 // Checked before anything else is done with them: these are the two
745 // strings an ungated caller supplies, and they are persisted for the life
746 // of the realm.
747 mustBeUsableTitle(title)
748 if len(payload) > maxPayload {
749 panic( "govern: that payload is too long" )
750 }
751 k := g.kindOf(kind)
752 if err := g.checkKind(k, payload); err != nil {
753 panic( "govern: " + err.Error())
754 }
755
756 // The electorate is the checkpointed supply at the last sealed epoch. Not a
757 // hand-kept roll: the token already remembers who held what, and asking it is
758 // what makes the weight historical.
759 //
760 // THIS ALONE DOES NOT MAKE WEIGHT UNRENTABLE, and an earlier version of this
761 // comment claimed it did. The anchor is derived HERE, at propose time, by
762 // whoever proposes — so a renter buys float, waits ONE epoch for the
763 // checkpoint to seal, proposes (pinning the anchor to a window in which they
764 // hold), sells, and votes with weight they no longer own. Measured end to end:
765 // 300 B of pinned weight cast at 600x a quorum floor with a live balance of
766 // zero and one epoch of capital at risk. What this line buys on its own is
767 // only that weight cannot be acquired AFTER the question exists.
768 //
769 // CLOSED, and not here — the other half is supplied by the CONSUMER, through
770 // VoteWithCap, as a ceiling of the voter's live balance. The engine still
771 // derives this snapshot and takes the lesser, so a consumer can only lower.
772 // See VOTEFLOOR.md; kourtv2 caps all three of its lanes and r/govern does not,
773 // which is why the exposure is described here rather than fixed here.
774 at := g.voters.Epoch() - 1
775 if at == 0 {
776 panic( "govern: no sealed epoch yet — the chain is too young to vote" )
777 }
778 total := g.voters.PastTotal(at)
779 if total <= 0 {
780 panic( "govern: nothing was in issue at that epoch" )
781 }
782 if total > maxWeighable {
783 // The ceiling the tally silently assumed. grc20votes caps here so it never
784 // trips; a swapped-in electorate that does not would overflow yes*bps into
785 // a negative tally — a won vote reported as lost. Refused at the door,
786 // where it reads as a misconfigured electorate rather than the governor
787 // miscounting.
788 panic( "govern: the snapshot supply exceeds what a tally can weigh" )
789 }
790 // Snapshotted with the rest, so a bar cannot move under an open vote.
791 engaged := g.voters.EngagedTotal(at)
792 if engaged <= 0 {
793 // An electorate that engages nobody makes every quorum TRIVIAL, which is
794 // the opposite of what this comment said for a long time and the reason
795 // the guard is load-bearing rather than tidy. The bar is
796 // `cast*bps >= engaged*QuorumBps`; at engaged == 0 the right-hand side is
797 // zero and the comparison holds for any turnout, including none — so
798 // quorum stops existing, and at the deadline one yes vote carries the
799 // question however few showed up.
800 //
801 // Reachable with an honest electorate rather than a broken one:
802 // EngagedTotal is there so a realm can drop idle weight out of the
803 // denominator, and one where all weight is idle returns zero truthfully.
804 panic( "govern: the engaged weight has to be positive" )
805 }
806 if engaged > total {
807 // Clamp rather than refuse. A court deliberately hosts claims too big for
808 // its electorate to decide, and an EngagedTotal above the snapshot supply
809 // should make the bar unreachable-but-valid, not abort the proposal.
810 // Clamping to total keeps rest = total - cast non-negative in the tally.
811 // The token this ships with returns engaged == total, so this never fires
812 // for it — it is here for a replacement electorate.
813 engaged = total
814 }
815
816 rules := g.rulesOf(kind)
817 if rules.ProposeBps > 0 {
818 // The numbers, not just the verdict. The realm knows the bar and what
819 // the caller held; withholding both leaves somebody to work out from
820 // the terms page what they are short of, and the epoch matters most of
821 // all — a holder refused here may well hold plenty NOW and nothing at
822 // the sealed epoch this is weighed at, which reads as a bug rather than
823 // as the anti-flash-loan rule doing its job.
824 if held := g.voters.PastVotes(who, at); held*bps < total*rules.ProposeBps {
825 panic(ufmt.Sprintf( "govern: not enough voting power to open this: " +
826 "%s of the supply is required, and you held %d of %d at epoch %d" ,
827 pct(rules.ProposeBps), held, total, at))
828 }
829 }
830 // Everything may fill the board except the last few slots, which the
831 // governor's own kinds keep — see govLanes.
832 limit := maxLive
833 if !isReserved(kind) {
834 limit = maxOpen
835 }
836 if g.openIdx.Size() >= limit {
837 // Try to reclaim before refusing: a full list is usually full of
838 // finished business nobody has written down.
839 g.sweep()
840 }
841 if g.openIdx.Size() >= limit {
842 if limit < maxLive {
843 panic( "govern: too many proposals are already open; the last " +
844 "few slots are kept for the governor's own kinds" )
845 }
846 panic( "govern: too many proposals are already open" )
847 }
848 d := g.digest(kind, payload)
849 if v := g.openIdx.Get(d); v != nil {
850 // Settle the one already standing here before refusing on its account.
851 //
852 // The index holds finished proposals too — a read can work out that a
853 // proposal lost but cannot write it down, and the sweep above only
854 // runs when the list is FULL. So without this, a question defeated on
855 // its deadline and left unrecorded blocks the same question from being
856 // asked again, and the refusal says "already open" about something
857 // that closed.
858 //
859 // Targeted rather than a scan: the one proposal actually in the way,
860 // on the rare path where a digest collides at all.
861 if pv := g.proposals.Get(v.( string )); pv != nil {
862 g.settle(pv.(*proposal))
863 }
864 }
865 if g.openIdx.Has(d) {
866 panic( "govern: an identical proposal is already open" )
867 }
868 now := g.voters.Height()
869 g.propSeq++
870 p := &proposal{
871 id: g.propSeq, kind: kind, payload: payload, title: title, proposer: who,
872 rules: rules, epoch: at, total: total, engaged: engaged,
873 quorumFloor: quorumFloor,
874 opened: now, closes: now + rules.VotingBlocks,
875 openedTime: g.voters.Now(),
876 closesTime: g.voters.Now() + rules.VotingBlocks*secsPerBlock,
877 // The voter roll is bought here, by the proposer, including its first
878 // node — see rollSentinel just below the Set. Whoever asks, pays.
879 //
880 // The alternative is to let the first voter buy it, on the argument
881 // that a proposal nobody answers should cost nothing to leave
882 // unanswered. That saving only ever accrues to a proposal that failed,
883 // and it charges the first voter eleven times what the second pays.
884 // Being early is the wrong thing to tax.
885 //
886 // Fanout 32 on purpose. A narrower tree makes that first node cheaper
887 // (2,699 bytes at fanout 8 against 4,955) until the ninth voter, where
888 // the tree splits and that vote costs 5,476 — thirteen times what the
889 // eighth paid, for arriving in the wrong order. See
890 // docs/DESIGN.md.
891 state: stateActive, voted: bptree.NewBPTree32(),
892 }
893 p.voted.Set(rollSentinel, ballot( 0 ))
894 g.proposals.Set(enc( uint64 (g.propSeq)), p)
895 g.openIdx.Set(d, enc( uint64 (g.propSeq)))
896
897 chain.Emit(proposalOpenedEvent,
898 "id" , strconv.FormatInt(g.propSeq, 10 ),
899 "kind" , kind,
900 "proposer" , who.String(),
901 "epoch" , strconv.FormatUint( uint64 (at), 10 ),
902 )
903 return g.propSeq
904 }
905
906 // Vote records a choice, weighed at the proposal's snapshot.
907 func (g *Governor) Vote(who address, id int64 , choice string ) {
908 g.castVote(who, id, choice, "" , 0 )
909 }
910
911 // VoteWithCap is Vote where the consumer supplies a CEILING on the weight, and
912 // the engine still derives the weight itself and takes the lesser.
913 //
914 // A CEILING, NEVER A WEIGHT, and the distinction is the whole reason this method
915 // is shaped like this. An earlier attempt added VoteWithWeight, which took the
916 // figure to tally. That was a permissionless verdict flip.
917 //
918 // A supplied weight is not drawn from p.total, so `cast` could exceed it and
919 // `rest := p.total - cast` went NEGATIVE. Written out, the two early arms are
920 //
921 // early-succeed yes*bps >= (total - abstain)*T
922 // early-defeat (total - no - abstain)*bps < (total - abstain)*T
923 //
924 // because turnout is yes+no+abstain, so yes+no+rest is identically total-abstain.
925 // The damage was therefore an inflated NUMERATOR against a snapshotted denominator:
926 // `yes` could exceed anything the electorate held while (total - abstain) stayed
927 // put, passing on support that did not exist. And a negative rest shrinks
928 // (yes+rest), so the defeat arm could fire on a question still open.
929 //
930 // AN EARLIER VERSION OF THIS COMMENT SAID the arm "reduced to yes*bps >= (total -
931 // abstain)*T, dropping `no` out of the test entirely". The identity above shows
932 // that reduction is not a symptom of anything: it holds always, and `no` is not in
933 // the early-succeed comparison and never was. The description was repeated in four
934 // files before the algebra was checked, which is what
935 // TestTheEarlyArmsIgnoreNoAndACapOnlyDelays now pins. Here the consumer can only
936 // LOWER what this package read for itself, so
937 //
938 // Σ w ≤ Σ PastVotes(·, p.epoch) ≤ PastTotal(p.epoch) = p.total
939 //
940 // holds as an inequality rather than as a promise a caller has to keep. A hostile
941 // consumer cannot raise `cast`, and the contract electorate.gno says this engine
942 // "cannot check and cannot recover from" stays enforced where it lives.
943 //
944 // NO SENTINEL. `cap <= 0` is refused, and Vote is the uncapped path. Treating zero
945 // as "uncapped" was tried in the plan for this change and reopened the exploit
946 // verbatim: a renter who has sold everything HAS a floor of zero, so zero is not an
947 // edge case, it is the attack's terminal state.
948 //
949 // Why a consumer would want this: kourtv2 caps at the voter's live balance, so
950 // weight that has been sold back cannot vote. See VOTEFLOOR.md.
951 func (g *Governor) VoteWithCap(who address, id int64 , choice string , cap int64 ) {
952 if cap <= 0 {
953 panic( "govern: a vote cap must be positive; use Vote for no cap" )
954 }
955 g.castVote(who, id, choice, "" , cap)
956 }
957
958 // VoteWithReason is Vote with a note the voter wants on the record — the same
959 // pair OpenZeppelin has. Two entrypoints rather than one optional argument
960 // because MsgCall carries a fixed list of strings and gno has no optional
961 // parameters.
962 //
963 // The reason is emitted and never stored: it rides the Voted event, so the
964 // voter pays gas for the bytes and the ledger carries none of them. Storing it
965 // would be a new key per vote, on the path this design keeps cheapest.
966 //
967 // Bounded by maxReason. Not filtered through TextOnly, unlike a title: the
968 // realm never renders this on a page, so a consumer that displays it is
969 // responsible for its own escaping, as with any event field.
970 func (g *Governor) VoteWithReason(who address, id int64 , choice, reason string ) {
971 if len(reason) > maxReason {
972 panic( "govern: that reason is too long" )
973 }
974 g.castVote(who, id, choice, reason, 0 )
975 }
976
977 func (g *Governor) castVote(who address, id int64 , choice, reason string , cap int64 ) {
978 if g.executing {
979 panic( "govern: cannot vote from inside an execution or another vote" )
980 }
981 // AND THE LATCH IS HELD FOR THIS CALL, not merely read at the top of it.
982 //
983 // It was read here and written only in Execute, which means it guarded
984 // nothing on this path: a check with no corresponding write cannot exclude
985 // anything. The hazard is specific and it is this function's own ordering —
986 // the double-vote guard below reads p.voted, and p.voted is not written until
987 // after g.voters.PastVotes has been called. Between those two lines sits a
988 // call into an interface the CONSUMER supplies.
989 //
990 // So an Electorate whose PastVotes re-enters Vote for the same voter and
991 // proposal gets a nested frame that passes the executing check (false) and
992 // passes the already-voted check (not yet written), recurses, and adds its
993 // weight again on every unwind. mustProposal hands back a pointer, so every
994 // frame is adding to the same tally.
995 //
996 // Holding the latch for the whole call closes it at the door rather than by
997 // reordering the body, which also covers g.settle's own reads of
998 // g.voters.Height/Now above and below — the same class of call, made twice
999 // more on this path.
1000 //
1001 // A consumer whose electorate is its own ledger — which is the only shape in
1002 // this repo, kourtv2 passes its grc20votes ledger as both electorate and
1003 // token — cannot reach this. The guard is for the ones that are not.
1004 g.executing = true
1005 defer func () { g.executing = false }()
1006 p := g.mustProposal(id)
1007 g.settle(p)
1008 if p.state != stateActive {
1009 panic( "govern: that proposal is closed" )
1010 }
1011 if p.voted.Has( string (who)) {
1012 panic( "govern: already voted" )
1013 }
1014 // Weighed as of the snapshot, so buying in after the question was asked buys
1015 // nothing, and selling out afterwards costs nothing.
1016 //
1017 // THE SECOND HALF IS A KNOWN, MEASURED EXPOSURE and it is still not closed
1018 // HERE — it is closed by the caller, if the caller chooses to. VoteWithCap
1019 // takes a ceiling and the clamp below applies it, so a consumer that passes
1020 // the voter's live balance gets min(snapshot, held) and the rental dies;
1021 // plain Vote leaves the exposure exactly as propose() describes it. kourtv2
1022 // caps all three of its lanes; r/govern does not. VOTEFLOOR.md has the
1023 // derivation and why both halves are needed.
1024 //
1025 // A 29-line block describing a LIVE-BALANCE CAP stood here and was wrong twice
1026 // over: the cap was implemented, reverted in the same commit, and the prose
1027 // outlived it — in a commit whose subject was "correct a false safety claim".
1028 // It also argued against a design this file then shipped. Recorded because the
1029 // lesson is the expensive one: a comment describing a reverted mechanism is a
1030 // false safety claim with a plausible pedigree.
1031 w := g.voters.PastVotes(who, p.epoch)
1032 if w <= 0 {
1033 panic( "govern: no voting power at that epoch" )
1034 }
1035 // The consumer's ceiling, and it may only LOWER. Written as an explicit clamp
1036 // rather than a min() helper so that the one property this block exists for —
1037 // w is never raised — is a single readable line that a guard can pin.
1038 if cap > 0 && cap < w {
1039 w = cap
1040 }
1041 switch choice {
1042 case "yes" :
1043 p.yes += w
1044 case "no" :
1045 p.no += w
1046 case "abstain" :
1047 // Turnout, not indifference. Showing up to decline is a different
1048 // statement from silence, and it counts towards quorum while staying
1049 // out of the threshold.
1050 p.abstain += w
1051 default :
1052 panic( "govern: choice must be yes, no or abstain" )
1053 }
1054 // For a long time nothing read this — only Has() was ever asked of the
1055 // tree — and it was kept anyway, on the measurement that swapping the whole
1056 // value for a bool saves ONE byte per vote, because what a vote costs is
1057 // the address key and the tree's own entry overhead rather than the value
1058 // hanging off it. VoteOf reads it now, which is what the byte was for.
1059 p.voted.Set( string (who), packBallot(choice, w))
1060 g.settle(p)
1061
1062 // The reason rides along only when there is one. An empty attribute on
1063 // every vote would cost every voter gas to say nothing, and would leave an
1064 // indexer unable to tell a voter who declined to explain from one whose
1065 // client cannot ask.
1066 if reason == "" {
1067 chain.Emit(votedEvent,
1068 "id" , strconv.FormatInt(id, 10 ),
1069 "voter" , who.String(),
1070 "choice" , choice,
1071 "weight" , strconv.FormatInt(w, 10 ),
1072 )
1073 return
1074 }
1075 chain.Emit(votedEvent,
1076 "id" , strconv.FormatInt(id, 10 ),
1077 "voter" , who.String(),
1078 "choice" , choice,
1079 "weight" , strconv.FormatInt(w, 10 ),
1080 "reason" , reason,
1081 )
1082 }
1083
1084 // ballot is one vote as cast. The weight is what the tally used; the choice is
1085 // here so a kind can pay the side that voted with it, which is the whole reason
1086 // VoteOf exists — a scheme that pays only the winners gives a juror expecting to
1087 // lose no reason to turn up, which suppresses the honest side during exactly
1088 // the manipulation a vote is meant to stop.
1089 // A ballot is one int64, not a struct.
1090 //
1091 // The tree hands out each value as its own object for lazy loading, and a
1092 // struct there is a SECOND object per vote — measured at 846 bytes a vote
1093 // against 454 for a scalar. A vote is the one thing in this realm that happens
1094 // thousands of times, so 405 bytes of object header is the wrong place to spend
1095 // on tidiness.
1096 //
1097 // weight*4 + the choice. Two bits for three choices, and the weight is bounded
1098 // by maxSupply, which is MaxInt64/10000 — so the shift cannot overflow with
1099 // four orders of magnitude to spare.
1100 type ballot int64
1101
1102 const (
1103 ballotYes int64 = iota
1104 ballotNo
1105 ballotAbstain
1106 )
1107
1108 func packBallot(choice string , weight int64 ) ballot {
1109 switch choice {
1110 case "yes" :
1111 return ballot(weight<< 2 | ballotYes)
1112 case "no" :
1113 return ballot(weight<< 2 | ballotNo)
1114 case "abstain" :
1115 return ballot(weight<< 2 | ballotAbstain)
1116 }
1117 // Unreachable: castVote refuses anything else before it gets here. Panics
1118 // rather than defaulting, because a silent default would file a vote under
1119 // a choice nobody made.
1120 panic( "govern: not a choice: " + choice)
1121 }
1122
1123 func (b ballot) unpack() (choice string , weight int64 ) {
1124 switch int64 (b) & 3 {
1125 case ballotYes:
1126 choice = "yes"
1127 case ballotNo:
1128 choice = "no"
1129 default :
1130 choice = "abstain"
1131 }
1132 return choice, int64 (b) >> 2
1133 }
1134
1135 // rollSentinel is the key Propose writes into a new voter roll, so that the
1136 // roll's first node is bought by the person asking the question rather than by
1137 // whoever happens to answer it first.
1138 //
1139 // A bptree allocates nothing until its first key, and its leaf carries
1140 // fanout-sized backing arrays, so that first key costs about eleven times an
1141 // ordinary insert. Left to the first voter, that is a tax on being early —
1142 // which is the worst moment to put one, since a proposal with no votes yet is
1143 // the one that needs the first.
1144 //
1145 // The empty string, because it is the one key no address can be: an address is
1146 // bech32 and bech32 is never empty. Readers taking an address from outside are
1147 // still told to refuse it, since "" arrives as a valid Go string from anybody
1148 // who wants to ask.
1149 const rollSentinel = ""
1150
1151 // VoteOf is how an address voted, for a kind that has to pay them.
1152 //
1153 // One address at a time, on purpose. A list of every voter is an unbounded
1154 // return, and handing back the tree itself would be a live mutator holding this
1155 // realm's authority — so payouts are pull-based: each claimant asks about
1156 // themselves.
1157 //
1158 // Answers only while the roll survives. It is dropped by ReleaseRoll, so a kind
1159 // that pays out should either do it inside Do, where the roll is certainly
1160 // intact, or tell its claimants that the window closes when somebody reclaims
1161 // the deposit.
1162 func (g *Governor) VoteOf(id int64 , who address) (choice string , weight int64 , ok bool ) {
1163 p := g.mustProposal(id)
1164 if p.voted == nil || who == rollSentinel {
1165 return "" , 0 , false
1166 }
1167 v := p.voted.Get( string (who))
1168 if v == nil {
1169 return "" , 0 , false
1170 }
1171 choice, weight = v.(ballot).unpack()
1172 return choice, weight, true
1173 }
1174
1175 // wouldBe is what the rules and the clock say a proposal's state is, without
1176 // writing anything down.
1177 //
1178 // Split out from settle because reads must not mutate. Render walks every
1179 // proposal, and a settle-on-read there is an unbounded write hiding inside a
1180 // function that looks like a query — discarded harmlessly when it IS a query,
1181 // and a surprise when some realm calls Render inside a transaction.
1182 //
1183 // Every multiplication here is safe because the supply is capped at maxSupply.
1184 // yes, no and abstain are each bounded by the snapshot total, and total*bps
1185 // fits in int64 by construction.
1186 func (g *Governor) wouldBe(p *proposal, now int64 ) ( int8 , string ) {
1187 switch p.state {
1188 case stateActive:
1189 cast := turnout(p)
1190 // The bar: an absolute floor the consumer set for this proposal, or the
1191 // rules' QuorumBps fraction of the engaged weight when it did not.
1192 var quorum bool
1193 if p.quorumFloor > 0 {
1194 quorum = cast >= p.quorumFloor
1195 } else {
1196 quorum = cast*bps >= p.engaged*p.rules.QuorumBps
1197 }
1198 // Every vote not yet cast, all of which could still say no. Negative if
1199 // an electorate's parts exceed its whole, which this token cannot do
1200 // and a replacement might; a clamp here changed no outcome, because a
1201 // broken roll settles on the first vote either way. The contract is on
1202 // the electorate instead, where somebody swapping one will read it.
1203 rest := p.total - cast
1204 // p.yes > 0 is not redundant, and leaving it out was a real hole.
1205 //
1206 // The threshold compares yes against yes+no. When everybody ABSTAINS
1207 // both are zero, the comparison is 0 >= 0, and the proposal passed —
1208 // immediately, with nobody in favour, on the strength of an empty
1209 // denominator. Abstain is documented as turnout without support and it
1210 // was carrying proposals.
1211 //
1212 // Nothing passes without somebody voting for it, whatever the
1213 // threshold is set to.
1214 if quorum && p.yes > 0 && p.yes*bps >= (p.yes+p.no+rest)*p.rules.ThresholdBps {
1215 // Decided even if every remaining vote goes against. Waiting out
1216 // the clock on a settled question is latency, not deliberation —
1217 // and it is only computable because the denominator was
1218 // snapshotted, which is a third reason to snapshot.
1219 return stateSucceeded, ""
1220 }
1221 // And the same argument the other way, which was missing.
1222 //
1223 // If the threshold cannot be reached even with every remaining vote in
1224 // favour, the question is as settled as one that has already won — and
1225 // this side is the more common one, because a proposal that is going
1226 // to lose usually loses by everybody ignoring it.
1227 //
1228 // It was asymmetric for no reason anybody could have defended: a
1229 // decided YES closed at once while a decided NO sat out its deadline,
1230 // holding a slot in a list bounded at maxLive, asking a question that
1231 // had already been answered.
1232 if p.yes+rest == 0 {
1233 // Nobody in favour and nobody left who could be. Settled, whatever
1234 // the clock says — this is the all-abstained case, which now loses
1235 // at once instead of sitting out its deadline having already
1236 // failed.
1237 return stateDefeated, "nobody voted in favour"
1238 }
1239 if (p.yes+rest)*bps < (p.yes+rest+p.no)*p.rules.ThresholdBps {
1240 return stateDefeated, "threshold can no longer be reached"
1241 }
1242 if g.votingClosed(p, now) {
1243 if quorum && p.yes > 0 && p.yes*bps >= forAndAgainst(p)*p.rules.ThresholdBps {
1244 return stateSucceeded, ""
1245 }
1246 if !quorum {
1247 return stateDefeated, "quorum not reached"
1248 }
1249 return stateDefeated, "threshold not reached"
1250 }
1251 return stateActive, p.reason
1252
1253 case stateSucceeded:
1254 if g.expired(p) {
1255 // A proposal nobody executed for a long time is a decision about a
1256 // world that has moved. gno's own govdao has no deadline at all
1257 // and had to bolt on a reject path because proposals could sit
1258 // forever.
1259 return stateExpired, "not executed within the grace period"
1260 }
1261 }
1262 return p.state, p.reason
1263 }
1264
1265 // settle writes down what wouldBe worked out. Only ever called from a
1266 // transaction, because only a transaction can record anything.
1267 func (g *Governor) settle(p *proposal) {
1268 now := g.voters.Height()
1269 next, reason := g.wouldBe(p, now)
1270 if next == p.state {
1271 return
1272 }
1273 g.setState(p, next, reason)
1274 if next == stateSucceeded {
1275 // The delay runs from when the outcome is WRITTEN DOWN, not from when
1276 // the votes stopped mattering. Those differ for a proposal that wins on
1277 // its deadline: nobody has to be present when a deadline passes, so the
1278 // outcome is only recorded by the next transaction.
1279 //
1280 // Right way round — a timelock exists so people can react, and there is
1281 // nothing to react to until the decision is announced. Settle is
1282 // permissionless so announcing is never a privileged position.
1283 p.ready = now + p.rules.DelayBlocks
1284 p.readyTime = g.voters.Now() + p.rules.DelayBlocks*secsPerBlock
1285 return
1286 }
1287 }
1288
1289 // Execute runs a passed proposal. Permissionless on purpose: after the vote
1290 // and the delay there is nothing left to decide, so there is nobody left to
1291 // trust with the decision.
1292 // Dispatch runs an adopted kind. The consuming realm supplies it, because it is
1293 // the only thing that can: minting the sub-realm token a kind is handed needs a
1294 // live `cur`, and a /p/ package has none.
1295 //
1296 // It receives the kind, the sub-path to mint under, and the payload — and
1297 // NOTHING ELSE. No pointer into governor or ledger state passes through here,
1298 // which is what makes dispatching third-party code safe: gno-security-guide.md
1299 // §3(C) is about a victim invoking a caller-supplied value while holding its
1300 // own authority, and the damage in that class is done through a pointer
1301 // parameter. There is none to give.
1302 //
1303 // The realm's implementation is one line:
1304 //
1305 // func(k governor.Kind, sub, payload string) error {
1306 // return k.Do(0, cur.Sub(sub), payload)
1307 // }
1308 type Dispatch func (k Kind, subpath, payload string ) error
1309
1310 func (g *Governor) Execute(who address, id int64 , run Dispatch) {
1311 if run == nil {
1312 // The engine holds no capability of its own, so without a dispatcher
1313 // there is nothing that could run a kind. Refused before anything is
1314 // written, so a caller who forgot one can supply it and retry.
1315 panic( "govern: a dispatcher is required to run a kind" )
1316 }
1317 if g.executing {
1318 panic( "govern: re-entrant execution or vote" )
1319 }
1320 p := g.mustProposal(id)
1321 g.settle(p)
1322 if p.state != stateSucceeded {
1323 panic( "govern: that proposal is not waiting to be executed" )
1324 }
1325 if g.inDelay(p) {
1326 // Says when, because "not yet" without a when is an invitation to poll.
1327 // The proposal page has carried this figure all along; the refusal a
1328 // caller actually receives did not.
1329 //
1330 // In the unit that GOVERNS: a caller told "1,000 blocks away" has to know
1331 // the chain's pace to turn that into "come back tomorrow", and the pace
1332 // is what this conversion exists to stop assuming. A proposal settled
1333 // before the stamps keeps the block form, which is all it ever had.
1334 if p.readyTime != 0 {
1335 panic(ufmt.Sprintf( "govern: still in the delay window: executable at " +
1336 "%d (block %d), %d seconds away" ,
1337 p.readyTime, p.ready, p.readyTime-g.voters.Now()))
1338 }
1339 now := g.voters.Height()
1340 panic(ufmt.Sprintf( "govern: still in the delay window: executable at " +
1341 "height %d, %d blocks away" , p.ready, p.ready-now))
1342 }
1343 e := g.entryOf(p.kind)
1344 if e == nil || !e.live {
1345 // The holders withdrew this power while the proposal was waiting.
1346 // Recorded as a failure rather than a panic: the proposal is finished
1347 // either way, and a panic would leave it Succeeded forever, retried by
1348 // anyone, failing identically each time.
1349 g.setState(p, stateFailed, "the kind was retired before this could run" )
1350 return
1351 }
1352 k := e.kind
1353
1354 // Checked again, against the world as it is now rather than as it was when
1355 // the question was asked.
1356 if err := g.checkKind(k, p.payload); err != nil {
1357 g.setState(p, stateFailed, "no longer valid: " +err.Error())
1358 return
1359 }
1360
1361 // Deferred, not cleared on the next line. If Do panics the transaction
1362 // aborts and the flag rolls back with everything else — but a recover
1363 // anywhere between here and there would leave the latch set and the
1364 // governor permanently unable to run anything again.
1365 g.executing = true
1366 defer func () { g.executing = false }()
1367 // The kind gets a SUB-REALM, never the realm's own capability — which is
1368 // why the sub-path is computed here and handed out, rather than left to
1369 // whoever writes the Dispatch.
1370 //
1371 // A live cur inside a foreign kind's Do has IsCurrent() true and the
1372 // consuming realm's PkgPath, so it is not "the realm as data", it is that
1373 // realm's authority, handed to code the holders adopted for one purpose.
1374 // With it a kind can cross() into any realm that trusts the consumer and be
1375 // seen as it, issue its tokens, and drain its banker. Adopting a treasury
1376 // kind would grant reach over every realm that names this one.
1377 //
1378 // cur.Sub gives a distinct pkgpath and a distinct address, and the VM
1379 // refuses RealmIssue for a sub-realm outright. A governed realm therefore
1380 // gates on the sub-path of the power it granted, which is also more useful
1381 // than gating on the governor as a whole.
1382 var err error
1383 if b, ok := asBuiltin(k); ok {
1384 // The governor's own kinds act on the governor. They are handed it
1385 // directly and never a sub-realm: there is no outside world for them
1386 // to reach, so there is nothing to grant.
1387 err = b.run(g, run, p.payload)
1388 } else {
1389 err = run(k, subPathOf(p.kind), p.payload)
1390 }
1391
1392 if err != nil {
1393 g.setState(p, stateFailed, err.Error())
1394 return
1395 }
1396 g.setState(p, stateExecuted, "" )
1397 }
1398
1399 // ReleaseRoll drops a finished proposal's voter roll and refunds its deposit to
1400 // whoever calls.
1401 //
1402 // The roll does not go when a proposal settles, because a kind that pays out to
1403 // the people who voted has to ask after the fact, and pull-based claiming means
1404 // the last claim can be a long way after execution. So it is reclaimed on
1405 // request instead: nobody is obliged to, and whoever does is paid for it — the
1406 // same bargain Settle makes for the slot.
1407 //
1408 // Refused twice over. While a proposal is open, because Vote reads the roll to
1409 // refuse a second vote and dropping it early would let everybody vote twice.
1410 // And while it has SUCCEEDED but not yet run, because that is exactly the
1411 // window in which the kind has not read it yet — Execute is where a kind that
1412 // pays its voters looks, so a stranger could otherwise empty the roll in the
1413 // block before execution and leave the kind with nobody to pay. Waiting for the
1414 // execution to land costs the reclaimer nothing; there is no deadline on this.
1415 func (g *Governor) ReleaseRoll(who address, id int64 ) {
1416 p := g.mustProposal(id)
1417 g.settle(p)
1418 switch p.state {
1419 case stateActive:
1420 panic( "govern: that proposal is still open; its roll is what refuses a second vote" )
1421 case stateSucceeded:
1422 panic( "govern: that proposal has not run yet; the roll is what it runs against" )
1423 }
1424 p.voted = nil
1425 }
1426
1427 // Settle advances a proposal's state and frees its slot if it is finished.
1428 //
1429 // Needed because a read cannot persist: State and Render will tell you a
1430 // proposal is defeated and tell you again tomorrow, since the transition they
1431 // computed died with the query. Without a way to record it, the open list is a
1432 // resource anybody can exhaust — fill every slot with proposals that will lose
1433 // and nothing ever reclaims them.
1434 //
1435 // Permissionless, because it decides nothing: it writes down a conclusion the
1436 // rules already reached.
1437 func (g *Governor) Settle(who address, id int64 ) {
1438 g.settle(g.mustProposal(id))
1439 }
1440
1441 // sweep frees the slots of a few finished proposals. Bounded, and run when
1442 // somebody proposes — the moment a full list matters, paid for by whoever wants
1443 // the room. Unbounded here would make one unlucky proposer tidy the history.
1444 //
1445
1446 const sweepScan = 8
1447
1448 func (g *Governor) sweep() {
1449 // openIdx cannot exceed maxLive — Propose refuses at the bound — so the
1450 // whole ring is a handful of windows. Trying them until one frees a slot is
1451 // still a BOUNDED scan; the bound that matters here is the index's, not the
1452 // history's, and it is the history that the per-window limit exists to keep
1453 // one unlucky proposer from paying to tidy.
1454 for w := 0 ; w <= maxLive/sweepScan; w++ {
1455 before := g.openIdx.Size()
1456 g.sweepWindow()
1457 if g.openIdx.Size() < before {
1458 return
1459 }
1460 }
1461 }
1462
1463 func (g *Governor) sweepWindow() {
1464 const scan = sweepScan
1465
1466 // Collected first, acted on after: settle removes from openIdx, and bptree
1467 // says a tree must not be modified during iteration (tree.gno). Calling it
1468 // from the callback removed the key the cursor stood on.
1469 //
1470 // No failure was observed and that is not evidence of safety — at this size
1471 // the tree tolerates it. The symptom, if it does not, is a slot that never
1472 // comes back, months later, under load.
1473 seen := 0
1474 last := ""
1475 var ids [] string
1476 var stale [] string
1477 g.openIdx.Iterate(g.sweepFrom, "" , func (k string , v any ) bool {
1478 seen++
1479 last = k
1480 if g.proposals.Get(v.( string )) == nil {
1481 stale = append(stale, k)
1482 } else {
1483 ids = append(ids, v.( string ))
1484 }
1485 return seen >= scan
1486 })
1487 // Where to resume. A short window means the end of the index was reached,
1488 // so the next one starts over; otherwise it starts just past the last key
1489 // looked at. Appending a NUL gives the smallest string above a key, and the
1490 // keys are all the same length, so nothing can sort between the two.
1491 if seen < scan {
1492 g.sweepFrom = ""
1493 } else {
1494 g.sweepFrom = last + "\x00"
1495 }
1496
1497 for _, key := range ids {
1498 if p := g.proposals.Get(key); p != nil {
1499 g.settle(p.(*proposal))
1500 }
1501 }
1502 // Index entries whose proposal has gone entirely, which settle cannot
1503 // reach because there is nothing left to settle.
1504 for _, k := range stale {
1505 g.openIdx.Remove(k)
1506 }
1507 }
1508
1509 // Cancel withdraws a proposal. Only the proposer, only while it is still open.
1510 func (g *Governor) Cancel(who address, id int64 ) {
1511 p := g.mustProposal(id)
1512 if who != p.proposer {
1513 panic( "govern: only the proposer may cancel" )
1514 }
1515 g.settle(p)
1516 if p.state != stateActive {
1517 panic( "govern: too late to cancel" )
1518 }
1519 // The one transition out of active that the rules do not make.
1520 g.setState(p, stateCanceled, "" )
1521 }
1522
1523 // ----------------------------------------------------------------- reading --
1524
1525 // State advances the clock before answering, so a reader never sees a proposal
1526 // that is active only because nobody has poked it.
1527 func (g *Governor) State(id int64 ) string {
1528 p := g.mustProposal(id)
1529 // The pure form: asking what a proposal's state is must not change it.
1530 st, _ := g.wouldBe(p, g.voters.Height())
1531 // COULD HAVE (v2): `return stateName(st)`. This switch is character-identical
1532 // to stateName six hundred lines below, and an exhaustive switch written
1533 // twice is a trap -- add an eighth state and whichever copy you forget
1534 // renders it as "expired", since both reach that string through the default
1535 // arm. Left as deployed: p/governor is on gnoland-1 and deploys once.
1536 switch st {
1537 case stateActive:
1538 return "active"
1539 case stateDefeated:
1540 return "defeated"
1541 case stateSucceeded:
1542 return "succeeded"
1543 case stateExecuted:
1544 return "executed"
1545 case stateFailed:
1546 return "failed"
1547 case stateCanceled:
1548 return "canceled"
1549 default :
1550 return "expired"
1551 }
1552 }
1553
1554 // Preview renders what a payload would say, before anybody proposes it — so an
1555 // interface can show somebody their proposal, and Check refuses a malformed one
1556 // without spending a transaction.
1557 //
1558 // It also pins the thing the extension point rests on: govern calling INTO a
1559 // kind in another realm. A stored interface value is re-resolved from the store
1560 // on every call, so the method that runs is the offering realm's code.
1561 //
1562 // Reads whether or not the kind is adopted. What a payload would say is not a
1563 // power, and refusing to preview an unadopted one makes the adoption vote
1564 // harder to judge rather than safer.
1565 func (g *Governor) Preview(kind, payload string ) string {
1566 // The same bound Propose applies, at the cheaper door. Propose is a
1567 // transaction and this is a read, so an unbounded payload here is the
1568 // easier of the two to hand over — and it reaches the same Describe and
1569 // Check with it. Bounding one door and not the other is not bounding
1570 // anything.
1571 if len(payload) > maxPayload {
1572 return "that payload is too long"
1573 }
1574 e := g.entryOf(kind)
1575 if e == nil {
1576 return "no kind by that name has been offered"
1577 }
1578 out := g.describeKind(e.kind, payload)
1579 if err := g.checkKind(e.kind, payload); err != nil {
1580 out += "\n\n_This would be refused: " + err.Error() + "_"
1581 }
1582 if !e.live {
1583 out += "\n\n_This kind has not been adopted, so it cannot be proposed yet._"
1584 }
1585 return out
1586 }
1587
1588 // Describe is what the vote is about, rendered by the kind from the payload
1589 // alone — the same string Do will be handed.
1590 func (g *Governor) Describe(id int64 ) string {
1591 p := g.mustProposal(id)
1592 return g.describeOf(p)
1593 }
1594
1595 // HasVoted reports whether an address has already voted on a proposal.
1596 //
1597 // OpenZeppelin has hasVoted; without it the only way to find out is to send a
1598 // vote and be refused, spending a transaction to learn what the realm knows. A
1599 // wallet needs it before deciding whether to offer the buttons.
1600 //
1601 // Keeps answering after a proposal closes, because the roll survives until
1602 // somebody calls ReleaseRoll. Once it has been released this answers false, so
1603 // it is "is their vote still on record here", not "did they ever" — the
1604 // permanent record of who voted is the Voted events.
1605 func (g *Governor) HasVoted(id int64 , who address) bool {
1606 p := g.mustProposal(id)
1607 return p.voted != nil && who != rollSentinel && p.voted.Has( string (who))
1608 }
1609
1610 // secsPerBlock converts a VotingBlocks rule — configured in blocks, as every
1611 // governor rule is — into the wall-clock window it was chosen to mean.
1612 const secsPerBlock = int64 ( 5 )
1613
1614 // votingClosed reports whether a proposal has stopped taking votes.
1615 //
1616 // THE DEADLINE IS THE PROMISE, THE HEIGHT IS THE FALLBACK. Counted in blocks a
1617 // window advertised as "four days" is four days only while the chain holds its
1618 // assumed pace; the report that started this work showed a vote "closing in ~7
1619 // days" beside an answer dated five years earlier. A proposal opened before the
1620 // stamps existed has closesTime 0 and keeps the height it was opened under.
1621 func (g *Governor) votingClosed(p *proposal, now int64 ) bool {
1622 if p.closesTime != 0 {
1623 return g.voters.Now() >= p.closesTime
1624 }
1625 return now >= p.closes
1626 }
1627
1628 // TimingsAt is Timings' wall-clock half: when the proposal opened and when
1629 // voting closes, as unix seconds, or 0 for a proposal opened before the stamps.
1630 //
1631 // A SIBLING RATHER THAN A WIDER Timings, because Timings is consumed by more
1632 // than one realm and arity is the kind of change that should arrive with its
1633 // readers rather than ahead of them. Its first reader lands in the same commit
1634 // as this method — kourtv2's ClaimTimeline — which is the rule the clock plan
1635 // applies to constants and which holds just as well for a read.
1636 //
1637 // The heights stay on Timings and are still the reference; this is the number
1638 // the gate actually compares against, so a page built from it cannot promise a
1639 // close the governor will not honour.
1640 func (g *Governor) TimingsAt(id int64 ) (openedTime, closesTime int64 ) {
1641 p := g.mustProposal(id)
1642 return p.openedTime, p.closesTime
1643 }
1644
1645 // Timings is a proposal's clock: when it opened, when voting closes, when it
1646 // becomes executable, and when that chance lapses. The companion to Tally —
1647 // without it, anything showing a countdown parsed the page, which is prose
1648 // written for people.
1649 //
1650 // Two of these are zero rather than absent, and both zeroes mean something:
1651 //
1652 // ready == 0 the outcome is not recorded yet, so the timelock has not
1653 // started. Settle starts it, and anybody may call it.
1654 // expires == 0 it never expires, which is what a grace period of zero means
1655 // everywhere else in this realm.
1656 func (g *Governor) Timings(id int64 ) (opened, closes, ready, expires int64 ) {
1657 p := g.mustProposal(id)
1658 at, ever := expiresAt(p)
1659 if !ever {
1660 at = 0
1661 }
1662 return p.opened, p.closes, p.ready, at
1663 }
1664
1665 // Tally is the vote so far: yes, no, abstain, and the supply they are weighed
1666 // against.
1667 //
1668 // The fourth number is the one worth having. Every bar here is a fraction —
1669 // quorum of the snapshot supply, threshold of what was cast — so three counts
1670 // without their denominator cannot be checked against anything. It is the
1671 // supply at the proposal's snapshot epoch, not the supply now.
1672 func (g *Governor) Tally(id int64 ) (yes, no, abstain, total int64 ) {
1673 p := g.mustProposal(id)
1674 return p.yes, p.no, p.abstain, p.total
1675 }
1676
1677 // Render is the governance page.
1678 //
1679 // It matters more here than a Render usually does. This design's whole claim
1680 // over one that carries code is that what a voter reads IS what executes —
1681 // Describe and Do consume the same string. That claim is only worth anything
1682 // if somebody actually reads it, so the description is on the page rather than
1683 // behind a call nobody makes.
1684 func (g *Governor) render(path, notes string ) string {
1685 if path != "" {
1686 return g.renderOne(path)
1687 }
1688 out := ufmt.Sprintf( "# %s (%s)\n\n" , g.token.Name(), g.token.Symbol())
1689
1690 // The token first, because the governance below is only meaningful in
1691 // terms of it: every quorum is a fraction of this supply and every vote is
1692 // weighed out of it.
1693 out += ufmt.Sprintf( "- supply: %s %s (%d at %d decimals)\n" ,
1694 g.units(g.token.TotalSupply()), g.token.Symbol(), g.token.TotalSupply(), g.token.Decimals())
1695 out += notes
1696 out += ufmt.Sprintf( "- epoch: %d (votes are weighed at sealed epochs)\n\n" , g.voters.Epoch())
1697 out += "## governance\n\n"
1698
1699 var open, ready, done string
1700 nOpen := 0
1701 now := g.voters.Height()
1702
1703 // The open list IS openIdx: a proposal holds its slot until it reaches a
1704 // final state, so what is in there is exactly what belongs above the fold.
1705 // Bounded by maxLive rather than by how many questions have ever been
1706 // asked.
1707 g.openIdx.Iterate( "" , "" , func (_ string , v any ) bool {
1708 pv := g.proposals.Get(v.( string ))
1709 if pv == nil {
1710 return false
1711 }
1712 p := pv.(*proposal)
1713 st, _ := g.wouldBe(p, now)
1714 if st != stateActive && st != stateSucceeded {
1715 // Finished, and still holding its slot because nobody has written
1716 // that down. The scan below reaches it as history, which is what
1717 // it is.
1718 return false
1719 }
1720 if st == stateSucceeded {
1721 ready += proposalLine(p, st)
1722 } else {
1723 open += proposalLine(p, st)
1724 }
1725 nOpen++
1726 return false
1727 })
1728
1729 // Newest first, and it stops as soon as the tail is full.
1730 //
1731 // The cap is on the WORK, not just on what gets appended. One pass over
1732 // every proposal the realm has ever held, computing each state and
1733 // truncating at the end, would grow forever while the page stayed the same
1734 // size — and this is the read every holder makes.
1735 g.proposals.ReverseIterate( "" , "" , func (_ string , v any ) bool {
1736 if len(done) >= 2000 {
1737 return true
1738 }
1739 p := v.(*proposal)
1740 st, _ := g.wouldBe(p, now)
1741 if st == stateActive || st == stateSucceeded {
1742 return false // already above, from g.openIdx
1743 }
1744 done += proposalLine(p, st)
1745 return false
1746 })
1747
1748 // Split by what the reader is asked to DO, not by which list the realm
1749 // keeps them in. Both hold a slot, but a decided proposal waiting out its
1750 // timelock is not something to vote on, and putting it under a heading that
1751 // means "still being voted on" tells the reader to do the wrong thing.
1752 //
1753 // The slot count belongs to the page rather than to either section. nOpen
1754 // counts everything holding a slot, decided-and-unrun included, so under a
1755 // voting heading it would sit above a shorter list — and vanish entirely
1756 // when every slot is held by decided proposals, which is when being full
1757 // matters most.
1758 // And the lane, once it is the only thing left. Below that point the count
1759 // is the whole story; at or above it, a holder reads free slots and is
1760 // refused anyway, which is the page contradicting the realm.
1761 out += ufmt.Sprintf( "%d of %d slots in use" , nOpen, maxLive)
1762 if nOpen >= maxOpen {
1763 out += ufmt.Sprintf( "; the last %d are kept for the governor's own kinds, " +
1764 "so nothing else can be opened until one comes back" , maxLive-maxOpen)
1765 }
1766 out += ".\n\n"
1767 if open == "" {
1768 out += "_Nothing to vote on._\n"
1769 } else {
1770 out += "### to vote on\n\n" + open
1771 }
1772 if ready != "" {
1773 out += "\n### decided, waiting to be run\n\n" + ready
1774 }
1775 if done != "" {
1776 out += "\n### finished\n\n" + done
1777 }
1778 return out
1779 }
1780
1781 func (g *Governor) renderOne(path string ) string {
1782 id, err := strconv.ParseInt(path, 10 , 64 )
1783 if err != nil {
1784 return "# not a proposal id\n"
1785 }
1786 v := g.proposals.Get(enc( uint64 (id)))
1787 if v == nil {
1788 return "# no such proposal\n"
1789 }
1790 p := v.(*proposal)
1791 st, reason := g.wouldBe(p, g.voters.Height())
1792
1793 out := ufmt.Sprintf( "# #%d — %s\n\n" , p.id, p.title)
1794 // The description first, and rendered by the kind from the payload alone.
1795 // This is the text the vote is about.
1796 out += g.describeOf(p) + "\n\n"
1797 out += ufmt.Sprintf( "- kind: `%s`\n- proposer: %s\n- state: **%s**\n" ,
1798 p.kind, p.proposer.String(), stateName(st))
1799 if reason != "" {
1800 out += "- reason: " + reason + "\n"
1801 }
1802 // The snapshot supply, rendered as the header renders the live one — it is
1803 // the same quantity, and it was raw here and in units there.
1804 //
1805 // The tally BELOW stays raw on purpose. Its stated job is to let a reader
1806 // check the arithmetic rather than take the state on trust, and mixing
1807 // units into numbers somebody is about to multiply would take that away.
1808 // Two renderings of the same kind of figure, for two different jobs, on one
1809 // page — which is worth saying because it looks like the inconsistency it
1810 // was just fixed for.
1811 out += ufmt.Sprintf( "- weighed at epoch %d, when %s %s was in issue (%d)\n" ,
1812 p.epoch, g.units(p.total), g.token.Symbol(), p.total)
1813 out += ufmt.Sprintf( "\n## votes\n\n- yes %d\n- no %d\n- abstain %d\n" ,
1814 p.yes, p.no, p.abstain)
1815 // Both bars, as counted, so a reader can check the arithmetic rather than
1816 // take the state on trust.
1817 cast := turnout(p)
1818 // Percentages, not basis points. The realm counts in bps because integers
1819 // are exact where a float is an argument waiting to happen, and pct() exists
1820 // precisely so that choice does not reach the page — a holder asked to read
1821 // "needs 6600 bps" is being asked to do arithmetic to find out whether their
1822 // vote mattered.
1823 // Against ENGAGED, because that is what quorum divides by. Equal to total
1824 // for this realm; an electorate that drops idle weight would otherwise show
1825 // a bar nobody is being measured against.
1826 //
1827 // A consumer-set absolute floor is rendered as the count it is, not a
1828 // fraction of engaged — the tally compares cast against that number, so the
1829 // page must show it or it contradicts the state it is describing.
1830 if p.quorumFloor > 0 {
1831 out += ufmt.Sprintf( "\nturnout %d of %d, needs %d · " , cast, p.engaged, p.quorumFloor)
1832 } else {
1833 out += ufmt.Sprintf( "\nturnout %d of %d, needs %s · " , cast, p.engaged, pct(p.rules.QuorumBps))
1834 }
1835 // Three cases, not two. The threshold weighs yes against yes+no, so it has
1836 // nothing to weigh when every vote was an abstention — but that is not the
1837 // same as nobody voting, and collapsing them prints a line that contradicts
1838 // itself:
1839 //
1840 // turnout 1000 of 1000, needs 20.00% · no votes cast
1841 //
1842 // The whole supply voted. Saying so on the left and denying it on the right
1843 // is worst for the one case this realm treats most carefully: an
1844 // all-abstained proposal is the one that would otherwise pass with nobody
1845 // in favour, which is why the support bar exists at all.
1846 switch {
1847 case forAndAgainst(p) > 0 :
1848 out += ufmt.Sprintf( "yes is %d of %d cast, needs %s\n" ,
1849 p.yes, forAndAgainst(p), pct(p.rules.ThresholdBps))
1850 case p.abstain > 0 :
1851 out += "every vote was an abstention, so the threshold has nothing to weigh\n"
1852 default :
1853 out += "no votes cast\n"
1854 }
1855
1856 // When voting closes. The block below was written for the succeeded state
1857 // on the reasoning that it is the one state where a reader has something to
1858 // do and no way to know when — which was right, and stopped one state
1859 // short: an active proposal is where they have the most to do.
1860 //
1861 // The subtraction is safe. wouldBe returns a terminal state once now has
1862 // reached p.closes, so a proposal reading active has not.
1863 if st == stateActive {
1864 // THE DATE, not a block count. "closes in 120,960 blocks" is a promise
1865 // only a reader who knows the chain's pace can check, and the pace is not
1866 // a promise — that mismatch is what put a vote "closing in ~7 days" beside
1867 // an answer dated five years earlier. A proposal opened before the stamps
1868 // has no date and keeps the block form it was opened under.
1869 if p.closesTime != 0 {
1870 // A COUNTDOWN AND AN ABSOLUTE, because they answer different
1871 // questions: the count tells a reader how long they have, and the
1872 // date is what the gate will actually compare against, so a page
1873 // read at one moment is still checkable at the next. The old line
1874 // gave a count in BLOCKS, which is only a duration to a reader who
1875 // knows the chain's pace — and the pace is what was wrong.
1876 out += ufmt.Sprintf( "\n_Voting closes in %d seconds, at %d (block %d)._\n" ,
1877 p.closesTime-g.voters.Now(), p.closesTime, p.closes)
1878 } else {
1879 out += ufmt.Sprintf( "\n_Voting closes in %d blocks, at height %d._\n" ,
1880 p.closes-g.voters.Height(), p.closes)
1881 }
1882 }
1883
1884 // What happens next, for a decision that has been taken and not yet done.
1885 //
1886 // Without this the page says "succeeded" and stops, which is the one state
1887 // where a reader has something to DO and no way to know when. It is also
1888 // the state that looks most like nothing is happening: the proposal sits on
1889 // the open list, apparently unfinished, while a timelock it cannot see runs
1890 // down.
1891 if st == stateSucceeded {
1892 now := g.voters.Height()
1893 expiry, expires := expiresAt(p)
1894 switch {
1895 case p.state != stateSucceeded:
1896 // Decided and not yet written down — what a proposal winning on its
1897 // DEADLINE looks like, since nobody has to be present when one
1898 // passes.
1899 //
1900 // p.ready is zero until then, so the lines below would be
1901 // arithmetic against nothing — "succeeded" and "expired" printed
1902 // together. Saying what is actually needed is the useful thing
1903 // anyway, because anybody may Settle it.
1904 out += "\n_Decided, and nobody has recorded it yet. The delay " +
1905 "starts when somebody calls Settle — anybody may._\n"
1906 case g.inDelay(p):
1907 // SECONDS AND THE ABSOLUTE, for the reason the voting close carries
1908 // both: a count in blocks is a duration only to a reader who knows
1909 // the chain's pace. A proposal settled before the stamps keeps the
1910 // block form, which is all it ever had.
1911 if p.readyTime != 0 {
1912 out += ufmt.Sprintf( "\n_Waiting: executable in %d seconds, at %d (block %d)._\n" ,
1913 p.readyTime-g.voters.Now(), p.readyTime, p.ready)
1914 } else {
1915 out += ufmt.Sprintf( "\n_Waiting: executable in %d blocks, at height %d._\n" ,
1916 p.ready-now, p.ready)
1917 }
1918 case g.expired(p):
1919 out += "\n_Expired: nobody executed it in time._\n"
1920 case expires:
1921 out += ufmt.Sprintf( "\n**Executable now**, until height %d.\n" , expiry)
1922 default :
1923 out += "\n**Executable now.**\n"
1924 }
1925 }
1926 return out
1927 }
1928
1929 // proposerBar renders the stake a proposer must hold, where zero is not "none
1930 // required" so much as "no check at all".
1931 func proposerBar(bps int64 ) string {
1932 if bps <= 0 {
1933 return "anybody, holding nothing"
1934 }
1935 return pct(bps) + " of the supply"
1936 }
1937
1938 // turnout is every vote cast, abstentions included. One definition because it
1939 // was two: the tally computed it and the page computed it again, so the quorum
1940 // a proposal was DECIDED by and the figure a holder READ could diverge. That
1941 // line has already produced two page-versus-state contradictions.
1942 //
1943 // Abstain counts here because it counts towards quorum, which is why the
1944 // support bar exists separately.
1945 func turnout(p *proposal) int64 { return p.yes + p.no + p.abstain }
1946
1947 // forAndAgainst is what the threshold weighs: the votes that took a side.
1948 //
1949 // Abstentions are deliberately absent — they count towards quorum and not
1950 // towards the bar — and that rule was written once in the tally and again on
1951 // the page, which is how a page comes to disagree with the state it describes.
1952 func forAndAgainst(p *proposal) int64 { return p.yes + p.no }
1953
1954 // inDelay is whether a decided proposal is still inside its timelock.
1955 //
1956 // Execute refuses while this holds and the page counts down while it holds, and
1957 // those two must not disagree by even a block: a page offering to run what
1958 // Execute will refuse is the same defect as a page calling a live proposal
1959 // expired. One definition, so a boundary cannot be moved on one side only.
1960 func (g *Governor) inDelay(p *proposal) bool {
1961 if p.readyTime != 0 {
1962 return g.voters.Now() < p.readyTime
1963 }
1964 return g.voters.Height() < p.ready
1965 }
1966
1967 // expired reports whether a decided proposal is past its execution window.
1968 //
1969 // THE TWO WINDOWS MOVE TOGETHER, and that is why they convert together: the
1970 // delay exists so people can react, and the grace exists so a decision about a
1971 // world that has moved cannot be executed. Both are measured from p.ready, so
1972 // converting one and not the other would let a proposal leave its delay on one
1973 // clock and expire on the other — a gap where it is neither waiting nor
1974 // executable, or an overlap where it is both.
1975 func (g *Governor) expired(p *proposal) bool {
1976 if at, ever := expiresAtTime(p); ever {
1977 return g.voters.Now() > at
1978 }
1979 at, ever := expiresAt(p)
1980 return ever && g.voters.Height() > at
1981 }
1982
1983 // expiresAtTime is expiresAt in wall-clock seconds, or (0, false) for a
1984 // proposal settled before the stamps existed.
1985 func expiresAtTime(p *proposal) ( int64 , bool ) {
1986 if p.rules.GraceBlocks <= 0 || p.readyTime == 0 {
1987 return 0 , false
1988 }
1989 return p.readyTime + p.rules.GraceBlocks*secsPerBlock, true
1990 }
1991
1992 // expiresAt is when a decided proposal stops being executable, and whether it
1993 // ever does. saneRules refuses a grace of zero, so in practice it always does.
1994 //
1995 // One definition because it is easy to write three: the tally testing
1996 // `now > ready+grace`, the page testing it again, and the page printing the sum
1997 // a third time as the height it offers to run until. Three independent copies
1998 // of one deadline is how a page comes to print "succeeded" and "expired"
1999 // together.
2000 func expiresAt(p *proposal) ( int64 , bool ) {
2001 // Nothing expires before the outcome is written down, because the window is
2002 // measured from p.ready and p.ready is zero until Settle sets it. Adding a
2003 // grace period to nothing gives a deadline in the realm's first hour, which
2004 // is how a page once printed "succeeded" and "expired" together.
2005 //
2006 // The page guards that by checking the recorded state before it asks; the
2007 // guard belongs here, where the arithmetic is. Timings walked straight into
2008 // it the moment it exposed this to a caller who had not read the page's
2009 // comment.
2010 // saneRules refuses a grace of zero, so that arm is defence rather than
2011 // policy; p.ready is the live one.
2012 if p.rules.GraceBlocks <= 0 || p.ready == 0 {
2013 return 0 , false
2014 }
2015 return p.ready + p.rules.GraceBlocks, true
2016 }
2017
2018 // span renders a period in blocks, with roughly how long that is.
2019 //
2020 // Correctness never depends on the wall clock, but "120960 blocks to vote"
2021 // does not tell a reader whether that is an afternoon or a season. Both are
2022 // printed and the approximation is marked as one.
2023 //
2024 // Five seconds a block, the same assumption the bootstrap rules were chosen
2025 // under. At another cadence the block counts are still exact and the hours are
2026 // not, which is why they are hedged.
2027 func span(blocks int64 ) string {
2028 if blocks <= 0 {
2029 return "no period"
2030 }
2031 secs := blocks * 5
2032 switch {
2033 case secs < 60 * 60 :
2034 return ufmt.Sprintf( "%d blocks (about %s)" , blocks, plural(secs/ 60 , "minute" ))
2035 case secs < 48 * 60 * 60 :
2036 return ufmt.Sprintf( "%d blocks (about %s)" , blocks, plural(secs/ 3600 , "hour" ))
2037 default :
2038 return ufmt.Sprintf( "%d blocks (about %s)" , blocks, plural(secs/ 86400 , "day" ))
2039 }
2040 }
2041
2042 // plural writes a count with its unit, in the number the count deserves.
2043 //
2044 // "1 hours" reads as something a machine wrote, and a page that reads as
2045 // machine-written gets skimmed — which is a poor outcome for the text somebody
2046 // is meant to study before granting a power.
2047 func plural(n int64 , unit string ) string {
2048 if n == 1 {
2049 return "1 " + unit
2050 }
2051 return ufmt.Sprintf( "%d %ss" , n, unit)
2052 }
2053
2054 // units renders a base-unit amount the way a holder counts it. The ledger is
2055 // integers all the way down, which is exact and unreadable: "supply: 1000"
2056 // beside "decimals: 6" asks somebody to divide before they know whether they
2057 // hold a millionth of the token or all of it. Both are printed — this for
2058 // reading, the raw figure for checking.
2059 //
2060 // scale is 10^decimals for the token being rendered — computed, not a constant,
2061 // because Go has no constant exponentiation and the engine does not know what token
2062 // it renders until given one (TestTheEngineRendersTheTokensOwnScale checks it).
2063 // Decimals is CLAMPED to 18 first: 10^19 overflows int64 (and 10^k for k≥64 wraps to
2064 // 0, which would divide-by-zero in units()), so an unclamped power on a pathological
2065 // token would corrupt or panic Render. Render is a read, so a clamp on the rare bad
2066 // token is the right trade; grc20votes reports a small fixed decimals and is unaffected.
2067 func (g *Governor) scale() int64 {
2068 d := g.token.Decimals()
2069 if d > 18 {
2070 d = 18
2071 }
2072 out := int64 ( 1 )
2073 for i := 0 ; i < d; i++ {
2074 out *= 10
2075 }
2076 return out
2077 }
2078
2079 func (g *Governor) units(n int64 ) string {
2080 whole := n / g.scale()
2081 frac := n % g.scale()
2082 // The sign has to be taken from n, not from the whole part. Integer
2083 // division truncates towards zero, so everything between -1 and 0 has a
2084 // whole part of exactly 0 — and a "-0" that FormatInt prints as "0". Taking
2085 // the minus from whole therefore dropped it for that entire range, and
2086 // -0.5 rendered as 0.5: not a near miss but the opposite number.
2087 sign := ""
2088 if n < 0 {
2089 sign = "-"
2090 whole, frac = -whole, -frac
2091 }
2092 out := strconv.FormatInt(frac, 10 )
2093 for len(out) < g.token.Decimals() {
2094 out = "0" + out
2095 }
2096 // Trailing zeroes are noise; a whole number should read as one.
2097 for len(out) > 1 && out[len(out)- 1 ] == '0' {
2098 out = out[:len(out)- 1 ]
2099 }
2100 if out == "0" {
2101 return sign + strconv.FormatInt(whole, 10 )
2102 }
2103 return sign + strconv.FormatInt(whole, 10 ) + "." + out
2104 }
2105
2106 // proposalLine is one row of the governance list.
2107 func proposalLine(p *proposal, st int8 ) string {
2108 return ufmt.Sprintf( "- [#%d](:%d) **%s** — `%s` · %s\n" ,
2109 p.id, p.id, p.title, p.kind, stateName(st))
2110 }
2111
2112 // maxReason bounds what a kind may write into the permanent record.
2113 //
2114 // An adopted kind is trusted — the holders voted it the power to run — so this
2115 // is not a defence against a hostile one, which could do far worse than store a
2116 // long string. It is a defence against a buggy one: an error carrying a whole
2117 // response body or a stack of context lands in a proposal that is kept forever
2118 // and rendered every time somebody opens it.
2119 const maxReason = 256
2120
2121 // clip truncates at a rune boundary, so a cut message stays valid UTF-8 rather
2122 // than ending in half a character.
2123 func clip(s string ) string {
2124 if len(s) <= maxReason {
2125 return s
2126 }
2127 // Walk back to the first byte that is not a continuation byte. Cutting
2128 // BEFORE that byte is already a whole-rune prefix — the character it
2129 // begins is simply left out.
2130 //
2131 // Backing off one further, to drop the lead byte as well, is wrong and
2132 // looked right: it takes the cut inside the character before, which is how
2133 // this was first written. The test that caught it decodes what survives
2134 // rather than measuring it.
2135 n := maxReason
2136 for n > 0 && s[n]& 0xC0 == 0x80 {
2137 n--
2138 }
2139 return s[:n] + "…"
2140 }
2141
2142 // proposalSettledEvent is the single announcement of an outcome.
2143 //
2144 // One event with a state on it rather than one event per outcome, because the
2145 // per-outcome shape is what let four of the six transitions go unannounced:
2146 // Executed and Failed were emitted at two of Execute's paths, its other
2147 // failure path and the retired-kind path said nothing, and a proposal that
2148 // simply LOST — the ordinary result — was never announced anywhere. It opened,
2149 // it collected votes, and then nothing was ever said about it again.
2150 const (
2151 // kindOfferedEvent is the only announcement a kind gets before it is
2152 // adopted, and the only one at all if it never is.
2153 //
2154 // The registry is never iterated — an ungated one is unbounded, and walking
2155 // it would put somebody's spam on the page holders read — so without this
2156 // an offered kind could be found only by guessing its name, and the
2157 // extension point was undiscoverable.
2158 //
2159 // Adoption gets no event: it happens through a proposal, which is already
2160 // announced when it opens and when it settles.
2161 kindOfferedEvent = "KindOffered"
2162
2163 proposalOpenedEvent = "ProposalOpened"
2164 votedEvent = "Voted"
2165 proposalSettledEvent = "ProposalSettled"
2166 )
2167
2168 // setState is the only writer of p.state, and does everything that follows from
2169 // a proposal reaching one, so no new code path has to remember. Called only
2170 // with a state out of stateActive: nothing transitions back in.
2171 //
2172 // Three things:
2173 //
2174 // - the reason is clipped, so a kind cannot write an unbounded string into a
2175 // record kept for the life of the realm;
2176 // - the slot goes, unless the proposal SUCCEEDED and is pending execution;
2177 // - the outcome is announced.
2178 //
2179 // Not the voter roll, which outlives the decision. A kind that pays the people
2180 // who voted has to ask after the proposal has finished, so the roll goes on
2181 // request instead, at ReleaseRoll — permissionless, with the deposit going to
2182 // whoever calls it, which is the same bargain Settle makes for the slot.
2183 func (g *Governor) setState(p *proposal, st int8 , reason string ) {
2184 p.state, p.reason = st, clip(reason)
2185 if st != stateSucceeded {
2186 g.release(p)
2187 }
2188 chain.Emit(proposalSettledEvent,
2189 "id" , strconv.FormatInt(p.id, 10 ),
2190 "state" , stateName(st),
2191 "reason" , p.reason,
2192 )
2193 }
2194
2195 func stateName(s int8 ) string {
2196 switch s {
2197 case stateActive:
2198 return "active"
2199 case stateDefeated:
2200 return "defeated"
2201 case stateSucceeded:
2202 return "succeeded"
2203 case stateExecuted:
2204 return "executed"
2205 case stateFailed:
2206 return "failed"
2207 case stateCanceled:
2208 return "canceled"
2209 }
2210 return "expired"
2211 }
2212
2213 func (g *Governor) entryOf(name string ) *entry {
2214 if v := g.kinds.Get(name); v != nil {
2215 return v.(*entry)
2216 }
2217 return nil
2218 }
2219
2220 // subPathOf maps a kind name onto a legal sub-realm path.
2221 //
2222 // Sub takes '/'-separated segments of [a-z0-9] with '_.-' inside a segment, and
2223 // a kind name is any printable ASCII without spaces — "govern:adopt" among
2224 // them. Anything outside the alphabet becomes '-', which keeps the mapping
2225 // total, stable and readable: a governed realm gating on this sees the power it
2226 // granted rather than the governor as a whole.
2227 func subPathOf(kind string ) string {
2228 out := [] byte (kind)
2229 for i := 0 ; i < len(out); i++ {
2230 c := out[i]
2231 switch {
2232 case c >= 'a' && c <= 'z' , c >= '0' && c <= '9' :
2233 case c >= 'A' && c <= 'Z' :
2234 out[i] = c + ( 'a' - 'A' )
2235 default :
2236 out[i] = '-'
2237 }
2238 }
2239 return string (out)
2240 }
2241
2242 // liveEntry is what a proposal needs: a kind the holders have adopted.
2243 //
2244 // The two failures are told apart because the remedy differs. A name nobody
2245 // offered is a typo; a name that IS offered and not adopted is waiting on a
2246 // vote, and "no such kind" points that reader at their spelling when what they
2247 // need is govern:adopt. Preview has always distinguished them.
2248 func (g *Governor) liveEntry(name string ) *entry {
2249 e := g.entryOf(name)
2250 if e == nil {
2251 panic( "govern: no such kind: " + name)
2252 }
2253 if !e.live {
2254 panic( "govern: the kind " + name + " has been offered but not adopted, " +
2255 "so it cannot be proposed yet — the holders adopt it with govern:adopt" )
2256 }
2257 return e
2258 }
2259
2260 func (g *Governor) kindOf(name string ) Kind { return g.liveEntry(name).kind }
2261
2262 // anyKind is the kind whether or not it is still adopted.
2263 //
2264 // Reading is not doing. Retiring a kind withdraws the power to run it; it must
2265 // not also erase the record of what was already proposed under it — and it did,
2266 // because everything that renders a proposal went through the live lookup and
2267 // panicked. One retirement would have taken every historical page with it.
2268 func (g *Governor) anyKind(name string ) Kind {
2269 e := g.entryOf(name)
2270 if e == nil {
2271 return nil
2272 }
2273 return e.kind
2274 }
2275
2276 // describeOf renders a proposal, surviving a kind that has since been retired
2277 // or was never offered here at all.
2278 func (g *Governor) describeOf(p *proposal) string {
2279 k := g.anyKind(p.kind)
2280 if k == nil {
2281 return "_the kind `" + p.kind + "` is no longer registered, so this " +
2282 "proposal can no longer describe itself. Its payload was:_\n\n" +
2283 g.indented(p.payload)
2284 }
2285 return g.describeKind(k, p.payload)
2286 }
2287
2288 // indented puts EVERY line of a payload inside the code block. Four spaces in
2289 // front of the whole string indents line one and leaves the rest as live
2290 // markdown, and payloads may contain newlines — govern:batch is defined in
2291 // terms of them.
2292 //
2293 // Reached only when a kind has been retired or was never registered, which is
2294 // the one place a payload is rendered with nothing left to have vetted it: the
2295 // validation belonged to that kind's Check.
2296 func (g *Governor) indented(s string ) string {
2297 return " " + strings.ReplaceAll(s, "\n" , "\n " )
2298 }
2299
2300 func (g *Governor) rulesOf(name string ) Rules { return g.liveEntry(name).rules }
2301
2302 func (g *Governor) mustProposal(id int64 ) *proposal {
2303 v := g.proposals.Get(enc( uint64 (id)))
2304 if v == nil {
2305 panic( "govern: no such proposal" )
2306 }
2307 return v.(*proposal)
2308 }
2309
2310 // Errors a kind returns. Values rather than strings built at the call site, so
2311 // a caller can compare them and Describe cannot drift from Check.
2312
2313 type govError struct { s string }
2314
2315 func (e *govError) Error() string { return e.s }
2316 func govErr(s string ) error { return &govError{s} }
2317
2318 // digest identifies a question by what it asks, not by who asked it.
2319 //
2320 // Only ever used to refuse a duplicate while one is open. Identity stays a
2321 // sequence number, because a number renders, sorts and paginates and a hash
2322 // does none of those — OpenZeppelin derives the whole proposal id from the
2323 // hash and pays for it every time a UI wants a list in order.
2324 func (g *Governor) digest(kind, payload string ) string {
2325 h := sha256.Sum256([] byte (kind + "\x00" + payload))
2326 return string (h[: 16 ])
2327 }
2328
2329 // release frees a proposal's slot in the open index. Idempotent, because
2330 // settle can reach a terminal state from more than one path.
2331 func (g *Governor) release(p *proposal) { g.openIdx.Remove(g.digest(p.kind, p.payload)) }
2332