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

grc20votes.gno

23.83 Kb · 649 lines
  1// Package grc20votes is a GRC20-shaped ledger that remembers what every
  2// holder's voting power used to be.
  3//
  4// An analog of OpenZeppelin's ERC20Votes, not a transliteration. Where the
  5// EVM's shape exists only because the EVM is what it is — opt-in delegation, a
  6// storage model that charges for every extra word forever — gno gets to do the
  7// obvious thing instead: delegation defaults to self, and the checkpoint rides
  8// in the account record a transfer was going to write anyway.
  9//
 10// # What a consumer does
 11//
 12// A realm allocates one Ledger and keeps it in an unexported package variable:
 13//
 14//	var ledger = grc20votes.NewLedger("Kourt Governance", "COURT", 6, 720)
 15//
 16//	func Transfer(cur realm, to address, amount int64) {
 17//		if !cur.IsCurrent() {
 18//			panic("stale realm")
 19//		}
 20//		ledger.Transfer(cur.Previous().Address(), to, amount)
 21//	}
 22//
 23// Nothing here takes a `cur realm`, because a /p/ package cannot declare a
 24// crossing function. That is not a limitation worked around — it is the right
 25// split. Authentication belongs to the realm that has a caller to authenticate;
 26// this package is told WHICH address is acting and does the bookkeeping.
 27//
 28// # Why it is safe to keep this in /p/
 29//
 30// Every field is unexported and no method hands back an interior pointer, so a
 31// consumer holds a *Ledger and nothing else. That matters more here than it
 32// looks: /p/-declared types can be named by other /p/ packages, so an exported
 33// *account or a method returning one would let a stranger declare a mutator
 34// over it — and gno's storage-realm borrow would run that mutator under the
 35// CONSUMING realm's authority. See gno-security-guide.md §3(B) and §4; this
 36// follows the encapsulation pattern p/demo/tokens/grc20 sets, with the
 37// authentication moved out to the realm rather than carried on a teller.
 38//
 39// The one exception is deliberate: Ledger has no method taking a callback, so
 40// there is nothing to launder through. Adding one later would need the
 41// parameter type declared in the consuming realm, not here.
 42package grc20votes
 43
 44import (
 45	"chain"
 46	"chain/runtime"
 47	"math/overflow"
 48	"strconv"
 49	"time"
 50
 51	checkpoint "gno.land/p/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/checkpoint/v0"
 52	bptree "gno.land/p/nt/bptree/v0"
 53)
 54
 55// Bps is the basis-point scale, named once because it is load-bearing in three
 56// places at once here and in any governor reading this ledger: the supply
 57// ceiling, a rendered percentage, and a quorum. A scale written out separately
 58// in each is a number that can come to disagree with itself.
 59const Bps = int64(10000)
 60
 61// MaxSupply keeps a tally's arithmetic honest for anything weighing this
 62// ledger. A threshold comparison is yes*Bps >= (yes+no)*threshold, and yes*Bps
 63// overflows int64 once the supply passes MaxInt64/Bps — a wrapped tally goes
 64// negative rather than failing loudly, so a won vote reports as lost.
 65//
 66// Not a remote bound: six decimals and a billion units in issue is 1e15 base
 67// units, already past it. Capped at mint, so a tally needs no overflow checks —
 68// roughly 922 million whole tokens at six decimals.
 69const MaxSupply = int64(9223372036854775807) / Bps
 70
 71// Event names are grc20's exactly (p/demo/tokens/grc20: types.gno
 72// TransferEvent, token.gno chain.Emit), so an indexer following
 73// Transfer/Approval sees the same stream a stock token produces.
 74//
 75// What this does NOT do is implement grc20.Teller. The SECURITY note on
 76// types.gno's Teller tells consumers to reject anything failing
 77// IsCanonicalTeller, so satisfying it would be worthless to a careful caller
 78// and misleading to a careless one.
 79const (
 80	mintEvent     = "Mint"
 81	burnEvent     = "Burn"
 82	transferEvent = "Transfer"
 83	approvalEvent = "Approval"
 84
 85	// delegateChangedEvent has no counterpart in grc20 because grc20 has no
 86	// delegation. The name is OpenZeppelin's, since anybody indexing a
 87	// governance token already knows it.
 88	delegateChangedEvent = "DelegateChanged"
 89)
 90
 91// sep joins the two halves of an allowance key. The checkpoint package's
 92// separator rather than a byte that happens to match it: two constants with the
 93// same value in different files eventually disagree.
 94const sep = checkpoint.Sep
 95
 96// supplyKey checkpoints the supply like any holder, under a key no address can
 97// take. "~" is not in the bech32 alphabet and an address always begins with
 98// "g". Not the separator itself, which the checkpoint package refuses as a key:
 99// one containing the separator shares a page range with its neighbours.
100const supplyKey = "~supply"
101
102// account is one holder: what they have, who votes it, and what it was.
103//
104// The votes series is a value field, not a pointer, so changing it dirties this
105// record and nothing else — which is why checkpointing is affordable here.
106//
107// The record outlives a zero balance. The stock ledger removes a holder at zero
108// (grc20 token.gno, led.balances.Remove); doing that here would delete the
109// voting history, and an account oscillating through zero would churn keys.
110type account struct {
111	balance  int64
112	delegate address // "" means self — see Delegate
113	votes    checkpoint.Series
114}
115
116// Ledger is one token's balances and the history of their voting power.
117//
118// Allocated by the consuming realm, which is what makes this work at all: the
119// trees inside carry that realm's storage stamp, so a method here borrows the
120// consumer's authority for the write and the storage is billed to them. A /p/
121// package's own state is frozen after init and could hold none of this.
122// Clock is where this ledger's sense of height comes from. A realm supplies
123// one so its OWN clock governs epochs; nil means the chain's, which is what an
124// ordinary deployment wants.
125//
126// It exists because a realm that can fast-forward its clock for testing cannot
127// fast-forward this package's: a pure package may not import a realm, and a
128// SETTABLE package global would be a capability any caller could take. Bound at
129// construction by the realm that owns the ledger, it is neither.
130type Clock interface {
131	// Height is the block height this ledger should quantise epochs by.
132	Height() int64
133
134	// Now is the same block's wall-clock time, unix seconds. The ledger itself
135	// quantises by HEIGHT and always will — epoch quantisation is what makes the
136	// anti-flash-loan property structural — but it passes this through to the
137	// governor on the Electorate interface, where a voting DEADLINE lives.
138	Now() int64
139}
140
141type Ledger struct {
142	// clock is nil on an ordinary deployment; see Clock.
143	clock    Clock
144	name     string
145	symbol   string
146	decimals int
147	id       string
148
149	// epochBlocks quantises the clock. Every change inside one epoch coalesces
150	// into a single update of a record already being written, which is what
151	// bounds archive growth by wall-clock activity rather than by block
152	// production — and it makes the anti-flash-loan property an invariant
153	// rather than a discipline, since a transaction cannot outlive its block
154	// and a block cannot outlive its epoch.
155	epochBlocks int64
156
157	accounts   *bptree.BPTree // address -> *account
158	allowances *bptree.BPTree // owner + sep + spender -> int64
159
160	// THIS ARCHIVE IS NEVER TRIMMED, and that is a property of the design rather
161	// than an omission. checkpoint.Archive has a Trim, the claim's hourly stake
162	// series uses it, and one line here would look like an ordinary storage
163	// saving.
164	//
165	// Trim's own contract is "ValueAt exact AT AND ABOVE keepFrom". Every question
166	// in the realm above is anchored at a PAST epoch and reads two values from
167	// here — PastVotes for each voter's ceiling and PastTotal for the bar those
168	// votes are judged against — so trimming past a live question's epoch makes
169	// both inexact, independently of each other. That is the numerator and the
170	// denominator drifting to different instants, which is the defect that
171	// produced turnout at 200-400% of its own bar and was reverted.
172	//
173	// What would have to be true to relax it: keepFrom below the oldest epoch any
174	// open question is anchored at, which means knowing every open question — a
175	// fact this package deliberately does not have. check-epoch-coherence arm 12
176	// pins the call sites meanwhile.
177	archive *checkpoint.Archive
178
179	supply checkpoint.Series
180	total  int64
181}
182
183// NewLedger creates an empty ledger. The identity is fixed here rather than
184// read from a constant, so one realm can run several and a court can name its
185// own coin.
186//
187// epochBlocks is the quantisation, in blocks. At gno's five-second cadence 720
188// is an hour, which is the figure the rest of this design was measured at.
189// NewLedgerWithClock is NewLedger with the height source named. A realm whose
190// own clock can be fast-forwarded passes itself; everyone else calls NewLedger.
191func NewLedgerWithClock(name, symbol string, decimals int, epochBlocks int64, clk Clock) *Ledger {
192	l := NewLedger(name, symbol, decimals, epochBlocks)
193	l.clock = clk
194	return l
195}
196
197func NewLedger(name, symbol string, decimals int, epochBlocks int64) *Ledger {
198	if name == "" || symbol == "" {
199		panic("grc20votes: a token needs a name and a symbol")
200	}
201	if decimals < 0 || decimals > 18 {
202		panic("grc20votes: decimals must be between 0 and 18")
203	}
204	if epochBlocks <= 0 {
205		panic("grc20votes: an epoch has to be at least one block long")
206	}
207	return &Ledger{
208		name: name, symbol: symbol, decimals: decimals,
209		// The pkgpath is not knowable from here, so the id is the symbol
210		// qualified by whatever the consumer wants. A realm that wants the
211		// grc20reg shape passes its own path in the name.
212		id:          symbol,
213		epochBlocks: epochBlocks,
214		accounts:    bptree.NewBPTree32(),
215		allowances:  bptree.NewBPTree32(),
216		archive:     checkpoint.NewArchive(),
217	}
218}
219
220// SetID names this token the way an indexer will see it, usually
221// "gno.land/r/you/realm.SYMBOL". Called once, at construction time, by the
222// realm that knows its own path — a /p/ package cannot ask.
223func (l *Ledger) SetID(id string) { l.id = id }
224
225// ------------------------------------------------------------------ reads --
226
227// The GRC20 reads, with the names that standard uses, answering about NOW.
228//
229// Governance does not read any of these. It asks PastVotes and PastTotal,
230// which answer about a sealed epoch — the distinction the whole design turns
231// on, and the reason these are grouped apart from them.
232
233func (l *Ledger) Name() string       { return l.name }
234func (l *Ledger) Symbol() string     { return l.symbol }
235func (l *Ledger) Decimals() int      { return l.decimals }
236func (l *Ledger) ID() string         { return l.id }
237func (l *Ledger) TotalSupply() int64 { return l.total }
238
239func (l *Ledger) BalanceOf(owner address) int64 {
240	if a := l.getAccount(owner); a != nil {
241		return a.balance
242	}
243	return 0
244}
245
246func (l *Ledger) Allowance(owner, spender address) int64 {
247	if v := l.allowances.Get(string(owner) + sep + string(spender)); v != nil {
248		return v.(int64)
249	}
250	return 0
251}
252
253// VotesOf is voting power right now — the sum of every balance delegated here,
254// including the holder's own unless they delegated it away.
255func (l *Ledger) VotesOf(who address) int64 {
256	if a := l.getAccount(who); a != nil {
257		return a.votes.Value()
258	}
259	return 0
260}
261
262// DelegateOf is who votes an address's balance. Self unless they said
263// otherwise, which is the default nobody has to remember.
264func (l *Ledger) DelegateOf(who address) address {
265	a := l.getAccount(who)
266	if a == nil || a.delegate == "" {
267		return who
268	}
269	return a.delegate
270}
271
272// PastVotes is voting power throughout a SEALED epoch. Refusing the current one
273// is the anti-flash-loan property as an invariant rather than a convention: a
274// transaction runs inside one block, a block inside one epoch, and this will
275// not answer for the epoch it is in.
276func (l *Ledger) PastVotes(who address, at uint32) int64 {
277	l.mustBeSealed(at)
278	a := l.getAccount(who)
279	if a == nil {
280		return 0
281	}
282	return a.votes.ValueAt(l.archive, string(who), at)
283}
284
285// PastTotal is every unit in existence during a sealed epoch, which a quorum is
286// a fraction of.
287//
288// Because delegation defaults to self, everyone's PastVotes sums to exactly
289// this. OZ cannot say so: getPastTotalSupply counts all supply while
290// getPastVotes counts only DELEGATED supply, so the denominator exceeds the
291// achievable numerator and quorum is unreachable where few have delegated.
292func (l *Ledger) PastTotal(at uint32) int64 {
293	l.mustBeSealed(at)
294	return l.supply.ValueAt(l.archive, supplyKey, at)
295}
296
297// EngagedTotal is what a governor measures quorum against. Every holder is
298// engaged here by definition, since power is self-delegated by default — a
299// realm wanting idle weight dropped out of the denominator wraps this.
300func (l *Ledger) EngagedTotal(at uint32) int64 { return l.PastTotal(at) }
301
302// WalkSupply visits the supply's CHANGE points, newest first, and stops early
303// when fn returns true. An epoch in which supply did not move is not a point:
304// a reader forward-fills between them.
305//
306// A WALK, not a per-epoch read. PastTotal answers one epoch and is the wrong
307// shape for drawing a line — a chart over a year of hourly epochs would be
308// 8,760 lookups to find the handful of epochs that actually moved. The archive
309// already stores only the movements, so this hands them over directly and the
310// cost is the number of mints, not the age of the court.
311//
312// Unsealed on purpose, unlike PastTotal: the newest point may be the running
313// epoch, which is exactly the value a live chart wants at its right edge. It
314// may still coalesce before the epoch seals, so it is a reading and not a
315// record.
316func (l *Ledger) WalkSupply(fn func(e uint32, v int64) bool) {
317	// The two hot slots hold the newest points and are not in the archive yet.
318	if e0 := l.supply.Since(); e0 != 0 {
319		if fn(e0, l.supply.Value()) {
320			return
321		}
322	}
323	if e1, prev := l.supply.Prev(); e1 != 0 {
324		if fn(e1, prev) {
325			return
326		}
327	}
328	l.archive.WalkDesc(supplyKey, fn)
329}
330
331// WalkAccounts hands every account's address and balance to fn, ascending by
332// address, and stops early when fn returns true.
333//
334// NO NEW STATE. The accounts tree is the ledger's own index and has always held
335// exactly this; what was missing was a way to read it as a set rather than one
336// address at a time. A consumer wanting a holder ranking would otherwise have
337// had to maintain a second index alongside every mint, burn and transfer — a
338// duplicate that can only ever drift out of step with the first.
339//
340// ZERO BALANCES ARE HANDED OVER TOO, deliberately. An account can reach zero and
341// stay in the tree — Remove would drop the delegate and the vote history with
342// it, which is why the balance path never removes — so filtering here would
343// quietly hide accounts that still carry voting state. The caller knows which
344// question it is asking; this one answers "what does the ledger hold".
345func (l *Ledger) WalkAccounts(fn func(who address, bal int64) bool) {
346	l.accounts.Iterate("", "", func(k string, v any) bool {
347		a, ok := v.(*account)
348		if !ok {
349			return false
350		}
351		return fn(address(k), a.balance)
352	})
353}
354
355// Epoch is now. A proposal snapshots Epoch()-1 and stores it; nothing
356// re-derives it later.
357func (l *Ledger) Epoch() uint32 {
358	// 1-based, so zero can mean "before this token existed" — which is what
359	// lets an untouched series answer correctly without storing anything.
360	return uint32(l.Height()/l.epochBlocks) + 1
361}
362
363// Height is this ledger's clock, and the governor's: it is on the Electorate
364// interface so a governor weighing votes uses the same height the ledger
365// quantised its snapshots by. Falling back to the chain when no clock was
366// supplied keeps every existing construction working unchanged.
367func (l *Ledger) Height() int64 {
368	if l.clock != nil {
369		return l.clock.Height()
370	}
371	return runtime.ChainHeight()
372}
373
374// Now is this ledger's wall clock, forwarded to the governor through the
375// Electorate interface. It is NOT used to quantise anything here: epochs are
376// height-quantised by design (see Clock), and this exists only so a deadline
377// the governor publishes can be a date.
378func (l *Ledger) Now() int64 {
379	if l.clock != nil {
380		return l.clock.Now()
381	}
382	return time.Now().Unix()
383}
384
385// EpochBlocks is the quantisation this ledger was built with, so a consumer
386// rendering a deadline in hours does not have to be told twice.
387func (l *Ledger) EpochBlocks() int64 { return l.epochBlocks }
388
389func (l *Ledger) mustBeSealed(at uint32) {
390	if at == 0 || at >= l.Epoch() {
391		panic("grc20votes: that epoch has not been sealed yet")
392	}
393}
394
395// ----------------------------------------------------------------- writes --
396//
397// Every one of these takes the acting address rather than reading it. The realm
398// above has the `cur realm` and is the only thing that can authenticate; being
399// handed an address here is what keeps this package free of a capability it
400// would have no way to check.
401
402// Transfer moves `from`'s own tokens.
403func (l *Ledger) Transfer(from, to address, amount int64) {
404	l.move(from, to, amount)
405}
406
407// Approve lets a spender move some of the owner's balance.
408func (l *Ledger) Approve(owner, spender address, amount int64) {
409	mustBeValid(spender)
410	if amount < 0 {
411		panic("grc20votes: negative allowance")
412	}
413	key := string(owner) + sep + string(spender)
414	if amount == 0 {
415		l.allowances.Remove(key)
416	} else {
417		l.allowances.Set(key, amount)
418	}
419	chain.Emit(approvalEvent,
420		"token", l.id,
421		"owner", owner.String(),
422		"spender", spender.String(),
423		"value", strconv.Itoa(int(amount)),
424	)
425}
426
427// TransferFrom spends an allowance.
428func (l *Ledger) TransferFrom(spender, from, to address, amount int64) {
429	key := string(from) + sep + string(spender)
430	allowed := l.Allowance(from, spender)
431	if allowed < amount {
432		panic("grc20votes: allowance exceeded")
433	}
434	// Debited before the move. Defence in depth rather than a live fix: nothing
435	// in move calls out, so today the order is unobservable and no test can
436	// tell the difference. It is written this way for the day something here
437	// does call out, when it becomes the difference between spending an
438	// allowance once and twice.
439	if rest := allowed - amount; rest == 0 {
440		l.allowances.Remove(key)
441	} else {
442		l.allowances.Set(key, rest)
443	}
444	l.move(from, to, amount)
445}
446
447// Mint creates tokens. This package holds no minter: who may call it is the
448// consuming realm's rule, because that is where the caller is known.
449func (l *Ledger) Mint(to address, amount int64) {
450	mustBeValid(to)
451	mustBePositive(amount)
452
453	next, ok := overflow.Add64(l.total, amount)
454	if !ok || next > MaxSupply {
455		panic("grc20votes: supply would exceed what a tally can weigh without overflowing")
456	}
457	l.total = next
458	l.supply.SetAt(l.archive, supplyKey, l.Epoch(), next)
459
460	a := l.openAccount(to)
461	a.balance += amount
462	l.addVotes(delegateeOf(to, a), amount)
463
464	// Both events, and the Transfer is the one that matters to anybody else.
465	//
466	// grc20 declares MintEvent and BurnEvent and emits neither: its Mint sends
467	// a TRANSFER with an empty `from` and its Burn one with an empty `to`. That
468	// is the ERC20 convention, and how anything written against the standard
469	// reconstructs balances — sum the Transfers, treat the empty counterparty
470	// as the supply change. Emitting only "Mint" matches the names while
471	// breaking what they are for.
472	//
473	// "Mint" is kept because it says plainly what happened. Anything totalling
474	// supply should total Transfers and not both, which is safe for a standard
475	// indexer since the standard never emits "Mint".
476	chain.Emit(transferEvent,
477		"token", l.id,
478		"from", "",
479		"to", to.String(),
480		"value", strconv.Itoa(int(amount)),
481	)
482	chain.Emit(mintEvent,
483		"token", l.id,
484		"to", to.String(),
485		"value", strconv.Itoa(int(amount)),
486	)
487}
488
489// Burn destroys `from`'s own tokens.
490func (l *Ledger) Burn(from address, amount int64) {
491	mustBePositive(amount)
492	a := l.getAccount(from)
493	if a == nil || a.balance < amount {
494		panic("grc20votes: insufficient balance")
495	}
496	a.balance -= amount
497	l.addVotes(delegateeOf(from, a), -amount)
498
499	l.total -= amount
500	l.supply.SetAt(l.archive, supplyKey, l.Epoch(), l.total)
501
502	// The other half of the convention: a burn is a Transfer to nowhere. See
503	// the note in Mint for why both events go out.
504	chain.Emit(transferEvent,
505		"token", l.id,
506		"from", from.String(),
507		"to", "",
508		"value", strconv.Itoa(int(amount)),
509	)
510	chain.Emit(burnEvent,
511		"token", l.id,
512		"from", from.String(),
513		"value", strconv.Itoa(int(amount)),
514	)
515}
516
517// Delegate points an address's voting power at someone else.
518//
519// Self-delegation is the DEFAULT, which OZ cannot afford: there it would put
520// two SSTOREs on every transfer forever, so it is opt-in and everybody is
521// surprised by it once. Here the checkpoint lives in a record the transfer
522// already writes.
523//
524// One hop, not transitive: if A delegates to B and B to C, A's weight sits with
525// B. Transitivity invites cycles and unbounded walks and buys nothing a second
526// call cannot.
527func (l *Ledger) Delegate(from, to address) {
528	mustBeValid(to)
529
530	if l.getAccount(from) == nil && to == from {
531		// Already self-delegated, because everybody is: DelegateOf reads a
532		// missing account and a blank delegate the same way, so a record here
533		// buys a key to store a fact true of every address that never existed.
534		//
535		// An address holding nothing CAN still name somebody else, and that has
536		// to persist for when it is funded. What it cannot usefully do is name
537		// itself.
538		return
539	}
540	a := l.openAccount(from)
541
542	was := delegateeOf(from, a)
543	if to == from {
544		a.delegate = "" // normalised, so "self" has one representation
545	} else {
546		a.delegate = to
547	}
548	now := delegateeOf(from, a)
549	if was == now {
550		return
551	}
552	l.addVotes(was, -a.balance)
553	l.addVotes(now, a.balance)
554
555	// The only state change here that nothing else reveals. Delegating moves
556	// power without moving a coin, so an indexer sees a holder's weight vanish
557	// with nothing to explain it, and the only alternative is polling
558	// DelegateOf for every address anyone has heard of.
559	//
560	// OZ also emits DelegateVotesChanged on every power change, which here
561	// would fire on every transfer, mint and burn — for something a caller can
562	// ask: VotesOf answers now, PastVotes for any sealed epoch. Emitting it
563	// would be transliteration.
564	chain.Emit(delegateChangedEvent,
565		"token", l.id,
566		"delegator", from.String(),
567		"fromDelegate", was.String(),
568		"toDelegate", now.String(),
569	)
570}
571
572// --------------------------------------------------------------- innards --
573
574func (l *Ledger) move(from, to address, amount int64) {
575	mustBeValid(to)
576	mustBePositive(amount)
577	src := l.getAccount(from)
578	if src == nil || src.balance < amount {
579		panic("grc20votes: insufficient balance")
580	}
581	dst := l.openAccount(to)
582
583	src.balance -= amount
584	dst.balance += amount
585
586	// Resolved once each. When both sides vote their own balance — the
587	// overwhelmingly common case — these are the records already in hand, so a
588	// checkpointed transfer dirties the same two objects a plain one would. The one
589	// exception is a side's FIRST write in a new epoch, which also rolls one point
590	// into the archive (one added page); amortised across the epoch's writes that
591	// is ~nothing, but it is not literally two objects at the boundary.
592	l.addVotes(delegateeOf(from, src), -amount)
593	l.addVotes(delegateeOf(to, dst), amount)
594
595	chain.Emit(transferEvent,
596		"token", l.id,
597		"from", from.String(),
598		"to", to.String(),
599		"value", strconv.Itoa(int(amount)),
600	)
601}
602
603func delegateeOf(self address, a *account) address {
604	if a.delegate == "" {
605		return self
606	}
607	return a.delegate
608}
609
610// addVotes moves voting power and checkpoints it.
611func (l *Ledger) addVotes(who address, delta int64) {
612	if delta == 0 {
613		return
614	}
615	a := l.openAccount(who)
616	next := a.votes.Value() + delta
617	if next < 0 {
618		panic("grc20votes: voting power would go negative")
619	}
620	a.votes.SetAt(l.archive, string(who), l.Epoch(), next)
621}
622
623func (l *Ledger) getAccount(who address) *account {
624	if v := l.accounts.Get(string(who)); v != nil {
625		return v.(*account)
626	}
627	return nil
628}
629
630func (l *Ledger) openAccount(who address) *account {
631	if a := l.getAccount(who); a != nil {
632		return a
633	}
634	a := &account{}
635	l.accounts.Set(string(who), a)
636	return a
637}
638
639func mustBeValid(a address) {
640	if !a.IsValid() {
641		panic("grc20votes: not an address")
642	}
643}
644
645func mustBePositive(amount int64) {
646	if amount <= 0 {
647		panic("grc20votes: amount must be positive")
648	}
649}