governor.gno
96.80 Kb · 2331 lines
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// 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.
24package governor
25
26import (
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.
53type 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.
98const 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.
105const maxWeighable = int64(9223372036854775807) / bps
106
107type 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
129const (
130 stateActive int8 = iota
131 stateDefeated
132 stateSucceeded
133 stateExecuted
134 stateFailed
135 stateCanceled
136 stateExpired
137)
138
139// proposal is one question, frozen.
140type 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.
204type 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.
224const 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.
248const 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.
253const maxOpen = maxLive - govLanes
254
255var (
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.
276type 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.
322const 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.
331func 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.
340func 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.
357func (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.
405const 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.
411const 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.
415const 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.
434func 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.
463func 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.
494func 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
503var 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.
513func 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
553func (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.
569func 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.
601func (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.
612type adoptKind struct{}
613
614func (k adoptKind) Name() string { return reserved + "adopt" }
615
616func (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
647func (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
658func (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.
669type retireKind struct{}
670
671func (k retireKind) Name() string { return reserved + "retire" }
672
673func (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
681func (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
695func (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.
710func (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.
721func (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
728func (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.
907func (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.
951func (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.
970func (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
977func (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.
1100type ballot int64
1101
1102const (
1103 ballotYes int64 = iota
1104 ballotNo
1105 ballotAbstain
1106)
1107
1108func 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
1123func (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.
1149const 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.
1162func (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.
1186func (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.
1267func (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// }
1308type Dispatch func(k Kind, subpath, payload string) error
1309
1310func (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.
1415func (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.
1437func (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
1446const sweepScan = 8
1447
1448func (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
1463func (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.
1510func (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.
1527func (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.
1565func (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.
1590func (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.
1605func (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.
1612const 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.
1621func (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.
1640func (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.
1656func (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.
1672func (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.
1684func (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
1781func (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".
1931func 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.
1945func 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.
1952func 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.
1960func (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.
1975func (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.
1985func 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.
2000func 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.
2027func 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.
2047func 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.
2067func (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
2079func (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.
2107func 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.
2119const 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.
2123func 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.
2150const (
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.
2183func (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
2195func 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
2213func (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.
2227func 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.
2248func (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
2260func (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.
2268func (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.
2278func (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.
2296func (g *Governor) indented(s string) string {
2297 return " " + strings.ReplaceAll(s, "\n", "\n ")
2298}
2299
2300func (g *Governor) rulesOf(name string) Rules { return g.liveEntry(name).rules }
2301
2302func (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
2313type govError struct{ s string }
2314
2315func (e *govError) Error() string { return e.s }
2316func 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.
2324func (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.
2331func (g *Governor) release(p *proposal) { g.openIdx.Remove(g.digest(p.kind, p.payload)) }