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

v0 source pure

Package governor is a proposal engine: it decides what a body of holders has agreed to, and it decides nothing about ...

Overview

Package governor is a proposal engine: it decides what a body of holders has agreed to, and it decides nothing about what agreement means.

THE SPLIT IS THE POINT. A Governor holds proposals, epochs, quorum floors, the roll of eligible voters and the tally; it does not hold a treasury, a token, or any idea of what "adopt this" should DO. Adoption produces a KIND and a PAYLOAD, both strings, and the realm that installed the governor is what turns those into an effect. r/govern is the worked consumer, and r/offerer is the fixture proving a SECOND realm can extend the same engine from a different package without being trusted by it.

Voting power is read from a snapshot, never from a live balance. The electorate is asked what an address COULD vote at the block a proposal opened, which is what stops a vote being bought after the question is known -- see p/kourt/grc20votes, which remembers exactly that, and p/kourt/checkpoint under it. A bar is frozen for the life of a proposal for the same reason: a live numerator against a moving denominator produced turnout above 100% of its own bar and a permissionless verdict flip. VOTELOCK.md is the argument.

Strings rather than structs throughout the proposal surface, and that is forced rather than chosen: MsgCall.Args is []string, so a struct payload cannot cross the transaction boundary at all. The comment on the payload type below records what that costs and why nothing better is available.

Sorting keys, and nothing else.

The clock moved to the ledger, which is what quantises history and so is the only thing that can answer what epoch it is. What is left here is the one encoding decision this realm makes for itself.

Constants 1

const MaxLive, MaxOpen, GovLanes, MaxTitle, MaxPayload, MaxKindName, MaxBatch, Bps, MaxReason, Reserved

 1const (
 2	// MaxLive is how many proposals may stand at once.
 3	MaxLive = maxLive
 4
 5	// MaxOpen is how many of those an ordinary kind may fill. The difference
 6	// is kept for the governor's own kinds, so a flood of one thing cannot
 7	// stop the holders changing the rules.
 8	MaxOpen = maxOpen
 9
10	// GovLanes is that difference.
11	GovLanes = govLanes
12
13	// The bounds on a proposal's three strings, which doc.gno publishes as a
14	// table because they decide what a proposer can say before finding out by
15	// transaction.
16	MaxTitle    = maxTitle
17	MaxPayload  = maxPayload
18	MaxKindName = maxKindName
19
20	// MaxBatch is how many members one govern:batch may carry.
21	MaxBatch = maxBatch
22
23	// Bps is the basis-point scale every bar here is expressed in.
24	Bps = bps
25
26	// MaxReason is the longest failure message a kind can put on the record.
27	// A kind returns the string and the governor keeps it for the life of the
28	// realm, so it is clipped rather than trusted.
29	MaxReason = maxReason
30
31	// Reserved is the prefix the governor's own kinds carry, which Offer
32	// refuses so that nothing published can render as a built-in.
33	Reserved = reserved
34)
source

The board's dimensions, which the front page prints and a caller may want to reason about before proposing.

Functions 4

func TextOnly

1func TextOnly(s string) error
source

TextOnly reports whether a string is text rather than page structure.

Exported because it is a kind author's problem. A payload comes from whoever proposes — any address at all when ProposeBps is zero — and Describe renders it onto a page others vote from, so a raw payload hands page structure to a stranger. The built-ins are safe by accident: their payloads are kind names and addresses, which cannot hold a newline. It bites the first kind that takes free text.

Example
1func (k mykind) Check(payload string) error {
2	if len(payload) > 280 {
3		return errTooLong
4	}
5	return govern.TextOnly(payload)
6}

Control characters, not printable-ASCII, so prose in any language survives — every byte of a multi-byte character is >= 0x80. Markdown within a line is allowed: it cannot leave the row it belongs to.

The governor cannot apply this to payloads itself; govern:batch is defined in terms of newlines. Only a kind knows its own grammar.

func New

1func New(voters Electorate, token Token) *Governor
source

New creates a governor with no kinds adopted and nothing proposed, and installs the built-in kinds that govern the governor itself.

The minter kind is NOT among them: who may create supply is a policy about a caller, and a pure package has no caller. A realm wanting one registers it with InstallBuiltin.

func BootstrapRules

1func BootstrapRules() Rules
source

BootstrapRules are the terms the governor governs ITSELF on.

Exported because a consuming realm registering a power of its own — the mint, most of all — should govern it on the same terms rather than inventing a second set that can drift from these.

func NewRules

1func NewRules(quorumBps, thresholdBps, votingBlocks, delayBlocks, graceBlocks, proposeBps int64) Rules
source

NewRules builds the terms a kind would pass on. A realm cannot allocate another realm's struct — govern.Rules{...} written elsewhere is refused with "cannot allocate ... in realm" — so the value comes into existence here and the caller passes numbers. Without it, Offer was uncallable from outside this package.

Types 6

type Dispatch

func
1type Dispatch func(k Kind, subpath, payload string) error
source

Execute runs a passed proposal. Permissionless on purpose: after the vote and the delay there is nothing left to decide, so there is nobody left to trust with the decision. Dispatch runs an adopted kind. The consuming realm supplies it, because it is the only thing that can: minting the sub-realm token a kind is handed needs a live `cur`, and a /p/ package has none.

It receives the kind, the sub-path to mint under, and the payload — and NOTHING ELSE. No pointer into governor or ledger state passes through here, which is what makes dispatching third-party code safe: gno-security-guide.md §3(C) is about a victim invoking a caller-supplied value while holding its own authority, and the damage in that class is done through a pointer parameter. There is none to give.

The realm's implementation is one line:

Example
1func(k governor.Kind, sub, payload string) error {
2	return k.Do(0, cur.Sub(sub), payload)
3}

type Electorate

interface
 1type Electorate interface {
 2	// PastVotes is what an address could vote at a sealed epoch.
 3	PastVotes(who address, at uint32) int64
 4
 5	// PastTotal is everything that existed then, which quorum is a fraction
 6	// of. Taken at the same epoch as the votes it is compared against, or a
 7	// quorum could be met against a supply that no longer exists.
 8	//
 9	// It must be at least the sum of every PastVotes at that epoch. The
10	// governor subtracts what has been cast from this to decide whether a
11	// question is already settled, so a roll whose parts exceed its whole
12	// reports votes that cannot exist and settles early. Measured, not feared:
13	// one voter reporting a hundred against a claimed total of ten carried a
14	// proposal at a 99% threshold.
15	//
16	// This realm's token satisfies it exactly, and a test says so after every
17	// operation that moves power. Anything swapped in here needs its own. The
18	// governor cannot check it and cannot recover from it — a clamp was tried.
19	//
20	// It must also stay at or below maxWeighable (MaxInt64/bps): the tally
21	// computes yes*bps, and a supply above that overflows int64 into a negative
22	// tally — a won vote reported as lost. grc20votes caps its supply there
23	// exactly; the governor refuses a proposal whose snapshot supply exceeds it,
24	// so a swapped-in electorate that ignores the ceiling fails loudly at the door.
25	PastTotal(at uint32) int64
26
27	// EngagedTotal is the part of PastTotal that quorum is measured against.
28	//
29	// It exists so a realm can drop idle weight out of the denominator without
30	// touching anything in the governor: a fixed low bar is unreachable at
31	// launch and trivial later, and moving the denominator is the fix that
32	// keeps the bar meaningful at both ends.
33	//
34	// Should be <= PastTotal at the same epoch. Weight outside it can still vote
35	// — it simply is not counted in the bar — so the governor keeps using
36	// PastTotal for how many votes could still be cast. If it exceeds PastTotal
37	// the governor clamps it down to PastTotal, so an electorate that over-reports
38	// engagement makes the bar unreachable-but-valid rather than aborting.
39	//
40	// This realm's token returns PastTotal unchanged: every holder is engaged
41	// by definition, since power is self-delegated by default.
42	EngagedTotal(at uint32) int64
43
44	// Epoch is now. A proposal snapshots Epoch()-1 and stores it; nothing
45	// re-derives it later.
46	Epoch() uint32
47
48	// Height is the block height the governor gates on: when voting opens and
49	// closes, when a delay has elapsed, when a proposal may execute.
50	//
51	// It comes from the electorate rather than from the chain so that a realm
52	// whose clock can be fast-forwarded moves the VOTES with it. Reading
53	// runtime.ChainHeight() here instead would leave a test chain able to age
54	// everything except the one thing a dispute waits on, and would let the
55	// governor and the ledger disagree about what epoch it is.
56	Height() int64
57
58	// Now is the block's wall-clock time in unix seconds, from the same source
59	// as Height and for the same reason: a realm that can fast-forward its clock
60	// must move the VOTES with it, and a pure package may not read the chain
61	// directly without leaving a test chain able to age everything except the
62	// one thing a dispute waits on.
63	//
64	// It exists because a voting window is a PROMISE TO A VOTER — "this closes
65	// in four days" — and a height only means a date if the chain's pace never
66	// changes. The governor gates on this and keeps the height beside it.
67	Now() int64
68}
source

Electorate is where voting weight comes from, and the only thing tying this engine to any particular token. Everything else here is about proposals, so with this extracted the engine can be driven by anything that can answer four questions.

It also makes the governor testable without a ledger. Reaching exactly a quorum with real balances means solving for the supply, and a test that does arithmetic to test arithmetic proves nothing.

EVERY QUESTION HERE IS ABOUT A SEALED PAST EPOCH, which is what keeps the Token/Electorate split below honest — Electorate about then, Token about now. A live per-address read (VotesOf) was added here while fixing rented weight and removed again when the fix was reverted; RENTEDWEIGHT.md records why. Anything re-adding it is re-opening that decision, not making a small change.

AND THE FIX THAT SHIPPED DID NOT NEED IT, which is the point worth keeping. The live figure arrives as a CAP from the consumer (VoteWithCap) instead of as an interface method the engine calls, so this interface stays entirely about THEN. The engine cannot read a live balance even if it wanted to, and a supplied ceiling can only lower what the engine derived — so coherence is structural rather than something each implementor has to get right.

type Governor

struct
 1type Governor struct {
 2	voters Electorate
 3	token  Token
 4
 5	kinds     *bptree.BPTree // name -> *entry
 6	proposals *bptree.BPTree // enc(id) -> *proposal
 7	openIdx   *bptree.BPTree // digest -> enc(id), the live ones
 8
 9	propSeq int64
10
11	// executing is the re-entrancy latch, and it is held for the WHOLE of every
12	// state-changing call — propose, castVote and Execute — not only Execute.
13	//
14	// It started as an Execute-only flag, against a kind calling back in while
15	// its own Do runs and seeing a proposal mid-execution. The other two read it
16	// and never wrote it, which made those reads decorative: propose and castVote
17	// both call into the consumer-supplied Electorate (PastVotes, PastTotal,
18	// EngagedTotal, and Height/Now by way of settle) and both mutate afterwards,
19	// so a hostile or merely re-entrant electorate could nest a second call
20	// inside the first. castVote was the sharp one — its already-voted guard
21	// reads state that the same function does not write until after the external
22	// call, so a nested frame passed both checks and the tally took the weight
23	// twice.
24	//
25	// The name is kept because every panic and every test says it; what changed
26	// is the span, from one call to three.
27	//
28	// THE THREE PANICS GAINED A CLAUSE RATHER THAN NEW WORDING. Each said "from
29	// inside an execution", which stopped being the only cause the moment vote
30	// and propose began holding the latch too. Rewriting them outright broke
31	// TestExecutionCannotReEnterTheGovernor, which pins each message by
32	// substring — correctly, because a panic string is API for anyone matching on
33	// it. Appending "or a vote" makes them true without moving what was pinned.
34	executing bool
35
36	// sweepFrom is where the next slot reclaim starts scanning, and it rotates.
37	// The index is keyed by digest(kind, payload) and a proposer chooses their
38	// payload, so they choose their key: park a few long-running proposals on
39	// the lowest keys and a FIXED window never sees past them, freeing nothing
40	// while the rest of the list is finished business. Beating the lowest of
41	// fifty-six random keys is a few hundred hashes offline.
42	sweepFrom string
43}
source

Governor is one governing body: which powers it has adopted, every question it has been asked, and where its slot reclaim had got to.

Allocated by the consuming realm, which is what makes this a library rather than a realm. A /p/ package's own state is frozen after init, so nothing durable could live at package level; the trees below carry the consumer's storage stamp, and every method here borrows the consumer's authority back for the write.

Every field is unexported and no method hands one out. /p/-declared types can be named by other /p/ packages, so an exported *proposal or a method returning one would let a stranger declare a mutator over it — and the storage-realm borrow would run that mutator under the CONSUMING realm's authority. See gno-security-guide.md §3(B) and §4.

Methods on Governor

func Adopt

method on Governor
1func (g *Governor) Adopt(k Kind, r Rules)
source

Adopt installs a kind as live without a vote.

The consuming realm's, and nobody else's — reaching it needs the *Governor, which a realm keeps unexported. That is the same authority the realm already has by holding the engine at all, so this grants nothing new; what it does is make the un-voted path explicit and greppable rather than something a realm improvises by reaching into a tree.

For a realm's OWN powers, and for arming a governor in a test. Anything a stranger publishes goes through Offer and an adoption vote, which is the distinction the whole design turns on: publishing code and letting that code hold the governor's authority are different decisions taken by different actors.

A realm that exposes this through a crossing function has given governance away. So would exposing Propose without its rules, or Execute without its timelock; the engine cannot stop a consumer publishing the wrong thing, and says so here because this is the one most obviously worth not publishing.

func Cancel

method on Governor
1func (g *Governor) Cancel(who address, id int64)
source

Cancel withdraws a proposal. Only the proposer, only while it is still open.

func Describe

method on Governor
1func (g *Governor) Describe(id int64) string
source

Describe is what the vote is about, rendered by the kind from the payload alone — the same string Do will be handed.

func Digest

method on Governor
1func (g *Governor) Digest(kind, payload string) string
source

Digest is the key a question occupies in the open index, derived from the kind and payload that define it. Exported because it is a pure function of two public strings, and because what it is FOR is worth knowing: a proposer chooses their payload and so chooses their key, which is why the slot sweep rotates rather than always starting at the low end.

func EpochOf

method on Governor
1func (g *Governor) EpochOf(id int64) uint32
source

EpochOf is the sealed epoch a proposal's weight was read at. Fixed when the question was asked, and the reason buying in afterwards buys nothing.

func Execute

method on Governor
1func (g *Governor) Execute(who address, id int64, run Dispatch)
source

func HasRoll

method on Governor
1func (g *Governor) HasRoll(id int64) bool
source

HasRoll reports whether a proposal is still holding its voter roll, or has given it back through ReleaseRoll.

func HasVoted

method on Governor
1func (g *Governor) HasVoted(id int64, who address) bool
source

HasVoted reports whether an address has already voted on a proposal.

OpenZeppelin has hasVoted; without it the only way to find out is to send a vote and be refused, spending a transaction to learn what the realm knows. A wallet needs it before deciding whether to offer the buttons.

Keeps answering after a proposal closes, because the roll survives until somebody calls ReleaseRoll. Once it has been released this answers false, so it is "is their vote still on record here", not "did they ever" — the permanent record of who voted is the Voted events.

func InstallBuiltin

method on Governor
1func (g *Governor) InstallBuiltin(k Kind, r Rules)
source

InstallBuiltin adopts a kind under the reserved prefix, which Offer refuses.

For the consuming realm only, and it is unexported state that makes that true: reaching this needs the *Governor, which the realm keeps to itself. A realm uses it for a power that is its own rather than the engine's — the mint being the example.

func KindRules

method on Governor
1func (g *Governor) KindRules(name string) (Rules, bool)
source

KindRules is the terms a kind was adopted on. A copy: Rules is a plain struct of numbers, so handing one back gives away nothing to write through.

func KindStatus

method on Governor
1func (g *Governor) KindStatus(name string) (offered, live bool)
source

KindStatus is whether a name has been offered here, and whether the holders have adopted it. Both false for a name nobody has published.

func Offer

method on Governor
1func (g *Governor) Offer(who address, k Kind, r Rules)
source

Offer shelves a kind. It takes effect on nothing.

Ungated, because an offered kind can do nothing. What it buys is a name pointing at published, immutable code the holders are later asked to approve.

The two-step exists because MsgCall.Args is []string: an account cannot hand over a Kind, only a realm can construct one. If adopting required passing the value, only realms could grow the governor — and that decision has to belong to the holders. Offer is the realm's half; the vote is theirs.

func OpenSlots

method on Governor
1func (g *Governor) OpenSlots() int
source

OpenSlots is how many proposals are holding a slot, decided-and-unrun included. The number the front page prints beside MaxLive.

func Preview

method on Governor
1func (g *Governor) Preview(kind, payload string) string
source

Preview renders what a payload would say, before anybody proposes it — so an interface can show somebody their proposal, and Check refuses a malformed one without spending a transaction.

It also pins the thing the extension point rests on: govern calling INTO a kind in another realm. A stored interface value is re-resolved from the store on every call, so the method that runs is the offering realm's code.

Reads whether or not the kind is adopted. What a payload would say is not a power, and refusing to preview an unadopted one makes the adoption vote harder to judge rather than safer.

func Proposals

method on Governor
1func (g *Governor) Proposals() int
source

Proposals is how many questions have ever been asked here.

func Propose

method on Governor
1func (g *Governor) Propose(who address, kind, payload, title string) int64
source

Propose opens a question. One maketx call, two strings — which is the whole design and the reason the payload is not a struct.

The quorum is the rules' QuorumBps fraction of the engaged weight. A consumer that computes its own absolute bar — a court sizing quorum to a claim's open interest — uses ProposeWithQuorum instead.

func ProposeWithQuorum

method on Governor
1func (g *Governor) ProposeWithQuorum(who address, kind, payload, title string, quorumFloor int64) int64
source

ProposeWithQuorum is Propose with an ABSOLUTE turnout the question needs, replacing the rules' QuorumBps fraction for this one proposal. The figure is the consumer's to compute (a court's max(5% supply, min(1×X̄, ⅓ votable))) and is snapshotted here, so the bar cannot move under an open vote. A non-positive floor is refused — a caller wanting the bps formula calls Propose.

Additive: Propose is unchanged and passes zero, which every existing user does.

func QuorumFloor

method on Governor
1func (g *Governor) QuorumFloor(id int64) int64
source

QuorumFloor is the absolute turnout a proposal needs when it was opened with ProposeWithQuorum, or 0 when the quorum is the rules' QuorumBps fraction of the engaged weight. A court renders a floored proposal's bar from this; the engine already uses it in the tally and on its own page. Snapshotted at Propose, so it is the bar the voters actually face.

func Reason

method on Governor
1func (g *Governor) Reason(id int64) string
source

Reason is why a proposal ended, or the empty string while it is still open. The page prints it; this is the same string.

func ReleaseRoll

method on Governor
1func (g *Governor) ReleaseRoll(who address, id int64)
source

ReleaseRoll drops a finished proposal's voter roll and refunds its deposit to whoever calls.

The roll does not go when a proposal settles, because a kind that pays out to the people who voted has to ask after the fact, and pull-based claiming means the last claim can be a long way after execution. So it is reclaimed on request instead: nobody is obliged to, and whoever does is paid for it — the same bargain Settle makes for the slot.

Refused twice over. While a proposal is open, because Vote reads the roll to refuse a second vote and dropping it early would let everybody vote twice. And while it has SUCCEEDED but not yet run, because that is exactly the window in which the kind has not read it yet — Execute is where a kind that pays its voters looks, so a stranger could otherwise empty the roll in the block before execution and leave the kind with nobody to pay. Waiting for the execution to land costs the reclaimer nothing; there is no deadline on this.

func Render

method on Governor
1func (g *Governor) Render(path, notes string) string
source

Render is the front page, or one proposal's page when path names an id.

notes are extra lines the consuming realm puts under the token and above the governance — who may mint, most of all, which is the realm's fact rather than the engine's and cannot be inferred from silence.

Passed in rather than stored. A stored note is a WRITE, and this is a read: a realm calling SetNotes from its own Render turned reading a page into something that costs storage, which is the defect check-storage exists to catch and did.

func RollSize

method on Governor
1func (g *Governor) RollSize(id int64) int
source

RollSize is how many addresses are on a proposal's voter roll.

Zero can mean two things and HasRoll separates them: nobody has voted yet, or the roll was reclaimed after the proposal finished.

func SetElectorate

method on Governor
1func (g *Governor) SetElectorate(e Electorate)
source

SetElectorate changes where voting weight comes from, keeping everything else — the adopted kinds, every proposal, the slot cursor.

The consuming realm's, like Adopt, and for a narrow purpose: a realm that wraps its ledger to drop idle weight out of the quorum denominator swaps the wrapper in here rather than rebuilding the governor and losing its registry.

Only sensible before anything is open. A proposal snapshots the epoch it is weighed at but reads the electorate live, so changing it under an open vote moves the bar the voters were shown — which is the thing per-proposal rules exist to prevent, arrived at from the other side.

func Settle

method on Governor
1func (g *Governor) Settle(who address, id int64)
source

Settle advances a proposal's state and frees its slot if it is finished.

Needed because a read cannot persist: State and Render will tell you a proposal is defeated and tell you again tomorrow, since the transition they computed died with the query. Without a way to record it, the open list is a resource anybody can exhaust — fill every slot with proposals that will lose and nothing ever reclaims them.

Permissionless, because it decides nothing: it writes down a conclusion the rules already reached.

func Snapshot

method on Governor
1func (g *Governor) Snapshot(id int64) (engaged int64, epoch uint32)
source

Snapshot is a proposal's engaged weight and the sealed epoch both it and the votes were read at — snapshotted at Propose and reachable no other way (EpochOf gives the epoch alone). Engaged is the turnout DENOMINATOR the pool is shown against; for a proposal opened with an absolute quorum floor the bar is that floor, not a fraction of engaged — read it with QuorumFloor. Scalars, so nothing is handed out to write through.

func State

method on Governor
1func (g *Governor) State(id int64) string
source

State advances the clock before answering, so a reader never sees a proposal that is active only because nobody has poked it.

func StoredState

method on Governor
1func (g *Governor) StoredState(id int64) string
source

StoredState is the state that has been WRITTEN DOWN, which is not always the state a reader is shown.

State computes the outcome from the rules and the clock, because a proposal whose deadline passed is decided whether or not anybody has poked it. Only a transaction records that, so between the deadline and the next Settle the two differ — and the difference is the whole reason reading is free: a read works out the answer and stores nothing.

A caller wanting to know whether the slot has actually been given back, or whether there is anything left for Settle to do, wants this one.

func Sweep

method on Governor
1func (g *Governor) Sweep()
source

Sweep reclaims the slots of proposals that have finished but were never written down.

Propose already calls it when the board is full, which is the path that matters. This is the same thing on request, for a caller who would rather free a slot than be refused one — permissionless, like Settle, and for the same reason: it decides nothing, it only writes down what the rules already decided.

func Tally

method on Governor
1func (g *Governor) Tally(id int64) (yes, no, abstain, total int64)
source

Tally is the vote so far: yes, no, abstain, and the supply they are weighed against.

The fourth number is the one worth having. Every bar here is a fraction — quorum of the snapshot supply, threshold of what was cast — so three counts without their denominator cannot be checked against anything. It is the supply at the proposal's snapshot epoch, not the supply now.

func Timings

method on Governor
1func (g *Governor) Timings(id int64) (opened, closes, ready, expires int64)
source

Timings is a proposal's clock: when it opened, when voting closes, when it becomes executable, and when that chance lapses. The companion to Tally — without it, anything showing a countdown parsed the page, which is prose written for people.

Two of these are zero rather than absent, and both zeroes mean something:

Example
1ready == 0    the outcome is not recorded yet, so the timelock has not
2              started. Settle starts it, and anybody may call it.
3expires == 0  it never expires, which is what a grace period of zero means
4              everywhere else in this realm.

func TimingsAt

method on Governor
1func (g *Governor) TimingsAt(id int64) (openedTime, closesTime int64)
source

TimingsAt is Timings' wall-clock half: when the proposal opened and when voting closes, as unix seconds, or 0 for a proposal opened before the stamps.

A SIBLING RATHER THAN A WIDER Timings, because Timings is consumed by more than one realm and arity is the kind of change that should arrive with its readers rather than ahead of them. Its first reader lands in the same commit as this method — kourtv2's ClaimTimeline — which is the rule the clock plan applies to constants and which holds just as well for a read.

The heights stay on Timings and are still the reference; this is the number the gate actually compares against, so a page built from it cannot promise a close the governor will not honour.

func Vote

method on Governor
1func (g *Governor) Vote(who address, id int64, choice string)
source

Vote records a choice, weighed at the proposal's snapshot.

func VoteOf

method on Governor
1func (g *Governor) VoteOf(id int64, who address) (choice string, weight int64, ok bool)
source

VoteOf is how an address voted, for a kind that has to pay them.

One address at a time, on purpose. A list of every voter is an unbounded return, and handing back the tree itself would be a live mutator holding this realm's authority — so payouts are pull-based: each claimant asks about themselves.

Answers only while the roll survives. It is dropped by ReleaseRoll, so a kind that pays out should either do it inside Do, where the roll is certainly intact, or tell its claimants that the window closes when somebody reclaims the deposit.

func VoteWithCap

method on Governor
1func (g *Governor) VoteWithCap(who address, id int64, choice string, cap int64)
source

VoteWithCap is Vote where the consumer supplies a CEILING on the weight, and the engine still derives the weight itself and takes the lesser.

A CEILING, NEVER A WEIGHT, and the distinction is the whole reason this method is shaped like this. An earlier attempt added VoteWithWeight, which took the figure to tally. That was a permissionless verdict flip.

A supplied weight is not drawn from p.total, so `cast` could exceed it and `rest := p.total - cast` went NEGATIVE. Written out, the two early arms are

Example
1early-succeed  yes*bps                    >= (total - abstain)*T
2early-defeat   (total - no - abstain)*bps  < (total - abstain)*T

because turnout is yes+no+abstain, so yes+no+rest is identically total-abstain. The damage was therefore an inflated NUMERATOR against a snapshotted denominator: `yes` could exceed anything the electorate held while (total - abstain) stayed put, passing on support that did not exist. And a negative rest shrinks (yes+rest), so the defeat arm could fire on a question still open.

AN EARLIER VERSION OF THIS COMMENT SAID the arm "reduced to yes*bps >= (total - abstain)*T, dropping `no` out of the test entirely". The identity above shows that reduction is not a symptom of anything: it holds always, and `no` is not in the early-succeed comparison and never was. The description was repeated in four files before the algebra was checked, which is what TestTheEarlyArmsIgnoreNoAndACapOnlyDelays now pins. Here the consumer can only LOWER what this package read for itself, so

Example
1Σ w  ≤  Σ PastVotes(·, p.epoch)  ≤  PastTotal(p.epoch)  =  p.total

holds as an inequality rather than as a promise a caller has to keep. A hostile consumer cannot raise `cast`, and the contract electorate.gno says this engine "cannot check and cannot recover from" stays enforced where it lives.

NO SENTINEL. `cap <= 0` is refused, and Vote is the uncapped path. Treating zero as "uncapped" was tried in the plan for this change and reopened the exploit verbatim: a renter who has sold everything HAS a floor of zero, so zero is not an edge case, it is the attack's terminal state.

Why a consumer would want this: kourtv2 caps at the voter's live balance, so weight that has been sold back cannot vote. See VOTEFLOOR.md.

func VoteWithReason

method on Governor
1func (g *Governor) VoteWithReason(who address, id int64, choice, reason string)
source

VoteWithReason is Vote with a note the voter wants on the record — the same pair OpenZeppelin has. Two entrypoints rather than one optional argument because MsgCall carries a fixed list of strings and gno has no optional parameters.

The reason is emitted and never stored: it rides the Voted event, so the voter pays gas for the bytes and the ledger carries none of them. Storing it would be a new key per vote, on the path this design keeps cheapest.

Bounded by maxReason. Not filtered through TextOnly, unlike a title: the realm never renders this on a page, so a consumer that displays it is responsible for its own escaping, as with any event field.

type Kind

interface
 1type Kind interface {
 2	// Name is the registry key.
 3	Name() string
 4
 5	// Describe renders a payload for the people voting on it.
 6	//
 7	// It must be a total function of the payload ALONE. Read live state here
 8	// and two voters at different heights are deciding different questions
 9	// while looking at the same page.
10	Describe(payload string) string
11
12	// Check validates a payload against the world as it is now. Called when
13	// the proposal opens AND again immediately before Do.
14	//
15	// The second call is the point. A proposal that was sound a week ago need
16	// not be sound now, and the alternative is discovering that inside Do,
17	// half-applied.
18	Check(payload string) error
19
20	// Do performs it. Non-crossing, taking the realm as data, so a /p/ package
21	// could implement this unchanged — crossing functions are illegal outside
22	// a realm.
23	//
24	// Returning an error and panicking are different decisions. An error
25	// FINISHES the proposal: recorded failed, slot returned, no retry. Say so
26	// when the answer will not change.
27	//
28	// A panic ABORTS the transaction, writing nothing — including the failure
29	// — so the proposal stays Succeeded and anybody may try again. Say so when
30	// a retry could work, or when half-finishing is worse than not starting:
31	// an abort is the only rollback gno offers. govern:batch depends on it.
32	Do(_ int, rlm realm, payload string) error
33}
source

A proposal is a KIND and a PAYLOAD, both strings.

Forced, and better than the alternative. MsgCall.Args is []string (gno.land/pkg/sdk/vm/msgs.go) and convertArgToGno switches on gno.BaseOf(argT), so scalars and named scalars like `address` convert; a struct, interface, pointer or func panics with "unexpected type in contract arg". An entrypoint taking one is uncallable by transaction.

A []byte converts too, base64-decoded, so the string is a choice: a payload arrives as text a voter can read, where bytes would arrive as base64.

A proposer cannot supply a closure either. A realm can persist one, but MsgRun forces the ephemeral package private and the save walk refuses it (realm.go, "cannot persist function or method from the private realm").

The property that falls out is what OpenZeppelin cannot offer: Describe and Do consume the identical string, so what voters read IS what runs.

type Rules

struct
 1type Rules struct {
 2	QuorumBps int64 // of the snapshot supply, counting abstain as turnout
 3
 4	// ThresholdBps is the share of yes+no (abstain excluded) that yes must reach.
 5	// The comparison is INCLUSIVE — yes*bps >= (yes+no)*ThresholdBps — so at 5000
 6	// a 50-50 tie PASSES. A realm wanting a strict majority sets 5001 or more; the
 7	// bootstrap and gno's govdao use a 6600 supermajority.
 8	ThresholdBps int64
 9
10	// ProposeBps is what a proposer must hold, as a fraction of the snapshot
11	// supply, to open a question at all.
12	//
13	// Attention control, not spam control: opening a proposal already costs a
14	// new key's worth of deposit. The list is what holders read, and a list
15	// nobody reads is a governor nobody governs. Zero means anyone may
16	// propose — right for a small realm, wrong for a public token.
17	ProposeBps   int64
18	VotingBlocks int64
19	DelayBlocks  int64 // succeeded -> executable; the timelock
20	GraceBlocks  int64 // executable -> expired
21}
source

type Token

interface
1type Token interface {
2	Name() string
3	Symbol() string
4	Decimals() int
5	TotalSupply() int64
6}
source

Token is what a page needs to name the thing being voted: an identity and a live supply. Separate from Electorate because they answer about different times — Electorate about a sealed epoch, Token about now — and a realm may well have one without the other.

p/kourt/grc20votes.Ledger satisfies both, which is the ordinary case.

Imports 7

Source Files 8