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

kinds.gno

9.40 Kb · 315 lines
  1package bubble
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	governor "gno.land/p/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/governor/v0"
  8)
  9
 10// This realm's own powers, installed at init under the engine's reserved prefix (Offer refuses it, so nothing
 11// can impersonate one). Each is a setting the admin holds (admin.gno) put under the holders too, so the admin's
 12// every power can be taken away by vote — and one that is nobody's but theirs: the reserve.
 13//
 14//	govern:admin    "g1…" | "none"    hand the settings to that address, or destroy the admin for good
 15//	govern:minter   "g1…" | "none"    name who mints referrals, or nobody
 16//	govern:pause    "on" | "off"      stop or resume the referral mint
 17//	govern:daycap   N                 the daily ceiling, in whole BUBBLE
 18//	govern:reserve  "g1… N"           mint N whole BUBBLE from the reserve to that address (the only way it moves)
 19//	govern:signal   text              a question with no effect: the sense of the holders
 20//
 21// Describe reads the payload alone (two voters at different heights must see the same question); Check reads
 22// the world; Do changes it. A Do that returns an error finishes the proposal as failed; one that panics aborts
 23// the transaction and leaves it executable again.
 24
 25const none = "none"
 26
 27// The money kinds (everything but the signal) pass on kourt's own terms (Jae, room 201: "use the system from
 28// kourt.xyz … it should auto adjust"): a fifth of the supply turning out, two thirds of yes+no, a week to vote, two
 29// days' delay, a fortnight's grace — and the turnout bar ADJUSTS: it is the greater of that fifth and the mean
 30// turnout of the last few money votes (never above a third of the supply), kourt's min(X̄, votable/3) with the
 31// bootstrap fifth as the base. Nobody sets it; the holders' own habit does. A signal decides nothing and asks less.
 32const (
 33	ReserveMaxPer = 2_000_000 * one          // per proposal
 34	ReserveGap    = int64(90 * 24 * 60 * 60) // seconds between two reserve mints: a full drain is 25 votes over 6 years
 35	turnoutKeep   = 8                        // money votes the mean remembers
 36)
 37
 38var (
 39	reserveLast int64   // block time of the last reserve mint
 40	turnouts    []int64 // the last turnoutKeep money votes' turnout (yes+no+abstain), newest last
 41	noted       = map[int64]bool{}
 42)
 43
 44func installKinds() {
 45	money := governor.BootstrapRules()
 46	engine.InstallBuiltin(adminKind{}, money)
 47	engine.InstallBuiltin(minterKind{}, money)
 48	engine.InstallBuiltin(pauseKind{}, money)
 49	engine.InstallBuiltin(daycapKind{}, money)
 50	engine.InstallBuiltin(reserveKind{}, money)
 51	// A signal decides nothing: a lighter bar, so the holders can be asked often.
 52	engine.InstallBuiltin(signalKind{}, governor.NewRules(500, 5000, 3*24*60*60/5, 0, 14*24*60*60/5, 0))
 53}
 54
 55func isMoneyKind(kind string) bool {
 56	switch kind {
 57	case governor.Reserved + "reserve", governor.Reserved + "admin", governor.Reserved + "minter", governor.Reserved + "pause", governor.Reserved + "daycap":
 58		return true
 59	}
 60	return false
 61}
 62
 63// quorumFloor is the absolute turnout a money kind needs at the last sealed epoch: max(supply/5, min(X̄, supply/3)),
 64// X̄ the mean turnout of the last money votes. Zero for the rest (the rules' fraction alone).
 65func quorumFloor(kind string) int64 {
 66	if !isMoneyKind(kind) {
 67		return 0
 68	}
 69	at := ledger.Epoch() - 1
 70	if at == 0 {
 71		return 0 // the engine refuses the proposal itself: no sealed epoch yet
 72	}
 73	supply := ledger.PastTotal(at)
 74	floor := supply / 5
 75	if x := meanTurnout(); x > floor {
 76		if third := supply / 3; x > third {
 77			x = third
 78		}
 79		if x > floor {
 80			floor = x
 81		}
 82	}
 83	return floor
 84}
 85
 86func meanTurnout() int64 {
 87	if len(turnouts) == 0 {
 88		return 0
 89	}
 90	var sum int64
 91	for _, t := range turnouts {
 92		sum += t
 93	}
 94	return sum / int64(len(turnouts))
 95}
 96
 97// noteTurnout remembers a decided money vote's turnout, once, when it is settled or executed.
 98func noteTurnout(id int64) {
 99	if noted[id] {
100		return
101	}
102	switch engine.State(id) {
103	case "active", "canceled":
104		return
105	}
106	yes, no, abstain, _ := engine.Tally(id)
107	noted[id] = true
108	turnouts = append(turnouts, yes+no+abstain)
109	if len(turnouts) > turnoutKeep {
110		turnouts = turnouts[len(turnouts)-turnoutKeep:]
111	}
112	if len(noted) > 256 { // the map is a guard, not a record: a fresh one loses nothing that matters
113		noted = map[int64]bool{}
114	}
115}
116
117// MeanTurnout is what the bar follows, for the page and the box.
118func MeanTurnout() int64 { return meanTurnout() }
119
120type bubbleErr struct{ msg string }
121
122func (e *bubbleErr) Error() string { return "bubble: " + e.msg }
123func errf(s string) error          { return &bubbleErr{s} }
124
125func addrOrNone(payload, what string) error {
126	if payload == none {
127		return nil
128	}
129	if !address(payload).IsValid() {
130		return errf("not an address, and not \"" + none + "\"")
131	}
132	return nil
133}
134
135// ---- govern:admin
136
137type adminKind struct{}
138
139func (adminKind) Name() string { return governor.Reserved + "admin" }
140func (adminKind) Describe(p string) string {
141	if p == none {
142		return "destroy the admin — every setting moves only by vote from then on"
143	}
144	return "hand the settings (minter, pause, day cap) to " + p
145}
146func (adminKind) Check(p string) error {
147	if err := addrOrNone(p, "admin"); err != nil {
148		return err
149	}
150	if p != none && address(p) == admin {
151		return errf("that is already the admin")
152	}
153	if p == none && admin == "" {
154		return errf("the admin is already gone")
155	}
156	return nil
157}
158func (adminKind) Do(_ int, rlm realm, p string) error {
159	if p == none {
160		admin = ""
161		return nil
162	}
163	admin = address(p)
164	return nil
165}
166
167// ---- govern:minter
168
169type minterKind struct{}
170
171func (minterKind) Name() string { return governor.Reserved + "minter" }
172func (minterKind) Describe(p string) string {
173	if p == none {
174		return "name nobody as minter — the referral mint stops until a vote names one"
175	}
176	return "name " + p + " as the minter of referral rewards"
177}
178func (minterKind) Check(p string) error {
179	if err := addrOrNone(p, "minter"); err != nil {
180		return err
181	}
182	if p != none && address(p) == minter {
183		return errf("that is already the minter")
184	}
185	return nil
186}
187func (minterKind) Do(_ int, rlm realm, p string) error {
188	if p == none {
189		minter = ""
190		return nil
191	}
192	minter = address(p)
193	return nil
194}
195
196// ---- govern:pause
197
198type pauseKind struct{}
199
200func (pauseKind) Name() string { return governor.Reserved + "pause" }
201func (pauseKind) Describe(p string) string {
202	if p == "on" {
203		return "pause the referral mint"
204	}
205	return "resume the referral mint"
206}
207func (pauseKind) Check(p string) error {
208	if p != "on" && p != "off" {
209		return errf("expected \"on\" or \"off\"")
210	}
211	if (p == "on") == paused {
212		return errf("the mint is already " + p)
213	}
214	return nil
215}
216func (pauseKind) Do(_ int, rlm realm, p string) error {
217	paused = p == "on"
218	return nil
219}
220
221// ---- govern:daycap
222
223type daycapKind struct{}
224
225func (daycapKind) Name() string { return governor.Reserved + "daycap" }
226func (daycapKind) Describe(p string) string {
227	return "set the daily mint ceiling to " + p + " BUBBLE"
228}
229func (daycapKind) Check(p string) error {
230	n, err := strconv.ParseInt(p, 10, 64)
231	if err != nil || n < 0 || n > ReferralPool/one {
232		return errf("expected a number of whole BUBBLE, up to the referral pool")
233	}
234	return nil
235}
236func (daycapKind) Do(_ int, rlm realm, p string) error {
237	n, err := strconv.ParseInt(p, 10, 64)
238	if err != nil {
239		return errf("not a number")
240	}
241	dayCap = n * one
242	return nil
243}
244
245// ---- govern:reserve
246
247type reserveKind struct{}
248
249func (reserveKind) Name() string { return governor.Reserved + "reserve" }
250func (reserveKind) Describe(p string) string {
251	to, n, err := parseReserve(p)
252	if err != nil {
253		return "mint from the reserve: " + p
254	}
255	return "mint " + strconv.FormatInt(n, 10) + " BUBBLE from the reserve to " + to.String()
256}
257func (reserveKind) Check(p string) error {
258	_, n, err := parseReserve(p)
259	if err != nil {
260		return err
261	}
262	if reserveMinted+n*one > Reserve {
263		return errf("only " + whole(Reserve-reserveMinted) + " BUBBLE are left in the reserve")
264	}
265	if reserveLast != 0 && ledger.Now() < reserveLast+ReserveGap {
266		return errf("the reserve moved less than ninety days ago; the next mint can run after " + strconv.FormatInt(reserveLast+ReserveGap, 10))
267	}
268	return nil
269}
270func (reserveKind) Do(_ int, rlm realm, p string) error {
271	to, n, err := parseReserve(p)
272	if err != nil {
273		return err
274	}
275	if reserveMinted+n*one > Reserve || (reserveLast != 0 && ledger.Now() < reserveLast+ReserveGap) {
276		return errf("the reserve is not open to this mint") // Check ran just before; a batch member could have changed the world
277	}
278	reserveMinted += n * one
279	reserveLast = ledger.Now()
280	ledger.Mint(to, n*one)
281	return nil
282}
283
284func parseReserve(p string) (address, int64, error) {
285	parts := strings.Fields(p)
286	if len(parts) != 2 {
287		return "", 0, errf("expected \"g1… N\": an address and whole BUBBLE")
288	}
289	to := address(parts[0])
290	if !to.IsValid() {
291		return "", 0, errf("not an address")
292	}
293	n, err := strconv.ParseInt(parts[1], 10, 64)
294	if err != nil || n <= 0 || n > ReserveMaxPer/one {
295		return "", 0, errf("expected whole BUBBLE, between 1 and " + whole(ReserveMaxPer) + " in one proposal")
296	}
297	return to, n, nil
298}
299
300// ---- govern:signal
301
302type signalKind struct{}
303
304func (signalKind) Name() string           { return governor.Reserved + "signal" }
305func (signalKind) Describe(p string) string { return "signal (no effect): " + p }
306func (signalKind) Check(p string) error {
307	if p == "" {
308		return errf("a signal needs words")
309	}
310	if len(p) > 1000 {
311		return errf("a signal is under a thousand characters")
312	}
313	return governor.TextOnly(p)
314}
315func (signalKind) Do(_ int, rlm realm, p string) error { return nil }