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

electorate.gno

6.51 Kb · 145 lines
  1package governor
  2
  3import (
  4	bptree "gno.land/p/nt/bptree/v0"
  5)
  6
  7// Electorate is where voting weight comes from, and the only thing tying this
  8// engine to any particular token. Everything else here is about proposals, so
  9// with this extracted the engine can be driven by anything that can answer
 10// four questions.
 11//
 12// It also makes the governor testable without a ledger. Reaching exactly a
 13// quorum with real balances means solving for the supply, and a test that does
 14// arithmetic to test arithmetic proves nothing.
 15//
 16// EVERY QUESTION HERE IS ABOUT A SEALED PAST EPOCH, which is what keeps the
 17// Token/Electorate split below honest — Electorate about then, Token about now.
 18// A live per-address read (VotesOf) was added here while fixing rented weight and
 19// removed again when the fix was reverted; RENTEDWEIGHT.md records why. Anything
 20// re-adding it is re-opening that decision, not making a small change.
 21//
 22// AND THE FIX THAT SHIPPED DID NOT NEED IT, which is the point worth keeping. The
 23// live figure arrives as a CAP from the consumer (VoteWithCap) instead of as an
 24// interface method the engine calls, so this interface stays entirely about THEN.
 25// The engine cannot read a live balance even if it wanted to, and a supplied
 26// ceiling can only lower what the engine derived — so coherence is structural
 27// rather than something each implementor has to get right.
 28type Electorate interface {
 29	// PastVotes is what an address could vote at a sealed epoch.
 30	PastVotes(who address, at uint32) int64
 31
 32	// PastTotal is everything that existed then, which quorum is a fraction
 33	// of. Taken at the same epoch as the votes it is compared against, or a
 34	// quorum could be met against a supply that no longer exists.
 35	//
 36	// It must be at least the sum of every PastVotes at that epoch. The
 37	// governor subtracts what has been cast from this to decide whether a
 38	// question is already settled, so a roll whose parts exceed its whole
 39	// reports votes that cannot exist and settles early. Measured, not feared:
 40	// one voter reporting a hundred against a claimed total of ten carried a
 41	// proposal at a 99% threshold.
 42	//
 43	// This realm's token satisfies it exactly, and a test says so after every
 44	// operation that moves power. Anything swapped in here needs its own. The
 45	// governor cannot check it and cannot recover from it — a clamp was tried.
 46	//
 47	// It must also stay at or below maxWeighable (MaxInt64/bps): the tally
 48	// computes yes*bps, and a supply above that overflows int64 into a negative
 49	// tally — a won vote reported as lost. grc20votes caps its supply there
 50	// exactly; the governor refuses a proposal whose snapshot supply exceeds it,
 51	// so a swapped-in electorate that ignores the ceiling fails loudly at the door.
 52	PastTotal(at uint32) int64
 53
 54	// EngagedTotal is the part of PastTotal that quorum is measured against.
 55	//
 56	// It exists so a realm can drop idle weight out of the denominator without
 57	// touching anything in the governor: a fixed low bar is unreachable at
 58	// launch and trivial later, and moving the denominator is the fix that
 59	// keeps the bar meaningful at both ends.
 60	//
 61	// Should be <= PastTotal at the same epoch. Weight outside it can still vote
 62	// — it simply is not counted in the bar — so the governor keeps using
 63	// PastTotal for how many votes could still be cast. If it exceeds PastTotal
 64	// the governor clamps it down to PastTotal, so an electorate that over-reports
 65	// engagement makes the bar unreachable-but-valid rather than aborting.
 66	//
 67	// This realm's token returns PastTotal unchanged: every holder is engaged
 68	// by definition, since power is self-delegated by default.
 69	EngagedTotal(at uint32) int64
 70
 71	// Epoch is now. A proposal snapshots Epoch()-1 and stores it; nothing
 72	// re-derives it later.
 73	Epoch() uint32
 74
 75	// Height is the block height the governor gates on: when voting opens and
 76	// closes, when a delay has elapsed, when a proposal may execute.
 77	//
 78	// It comes from the electorate rather than from the chain so that a realm
 79	// whose clock can be fast-forwarded moves the VOTES with it. Reading
 80	// runtime.ChainHeight() here instead would leave a test chain able to age
 81	// everything except the one thing a dispute waits on, and would let the
 82	// governor and the ledger disagree about what epoch it is.
 83	Height() int64
 84
 85	// Now is the block's wall-clock time in unix seconds, from the same source
 86	// as Height and for the same reason: a realm that can fast-forward its clock
 87	// must move the VOTES with it, and a pure package may not read the chain
 88	// directly without leaving a test chain able to age everything except the
 89	// one thing a dispute waits on.
 90	//
 91	// It exists because a voting window is a PROMISE TO A VOTER — "this closes
 92	// in four days" — and a height only means a date if the chain's pace never
 93	// changes. The governor gates on this and keeps the height beside it.
 94	Now() int64
 95}
 96
 97// Token is what a page needs to name the thing being voted: an identity and a
 98// live supply. Separate from Electorate because they answer about different
 99// times — Electorate about a sealed epoch, Token about now — and a realm may
100// well have one without the other.
101//
102// p/kourt/grc20votes.Ledger satisfies both, which is the ordinary case.
103type Token interface {
104	Name() string
105	Symbol() string
106	Decimals() int
107	TotalSupply() int64
108}
109
110// New creates a governor with no kinds adopted and nothing proposed, and
111// installs the built-in kinds that govern the governor itself.
112//
113// The minter kind is NOT among them: who may create supply is a policy about a
114// caller, and a pure package has no caller. A realm wanting one registers it
115// with InstallBuiltin.
116func New(voters Electorate, token Token) *Governor {
117	if voters == nil || token == nil {
118		panic("governor: a governor needs an electorate and a token")
119	}
120	g := &Governor{
121		voters:    voters,
122		token:     token,
123		kinds:     bptree.NewBPTree32(),
124		proposals: bptree.NewBPTree32(),
125		openIdx:   bptree.NewBPTree32(),
126	}
127	g.installBuiltins()
128	return g
129}
130
131// InstallBuiltin adopts a kind under the reserved prefix, which Offer refuses.
132//
133// For the consuming realm only, and it is unexported state that makes that
134// true: reaching this needs the *Governor, which the realm keeps to itself.
135// A realm uses it for a power that is its own rather than the engine's — the
136// mint being the example.
137func (g *Governor) InstallBuiltin(k Kind, r Rules) {
138	name := k.Name()
139	if !isReserved(name) {
140		panic("governor: a built-in has to carry the reserved prefix")
141	}
142	mustBeUsableName(name)
143	g.mustBeSaneRules(r)
144	g.kinds.Set(name, &entry{kind: k, rules: r, live: true})
145}