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 }