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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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 }