administration.gno
7.67 Kb · 260 lines
1package bolo
2
3import (
4 "chain"
5 "chain/runtime"
6)
7
8// Administration (API §3.6). Every write here starts with C11's realm branch, then
9// assertSigner: the master key, never a session key, in either branch, and then the
10// admin's address — for AcceptAdmin, the proposed admin's. The rules on the values
11// live in plain helpers, which unit tests drive directly: a crossing call's panic
12// can't be recovered by a test, so the writes' refusals are tested in txtar (plan
13// §3.3).
14
15// assertCodeCaller refuses any caller but a deployed realm (API C11). A maketx run
16// script is ephemeral and code at once: it is refused here, as is a user.
17func assertCodeCaller(cur realm) {
18 if p := cur.Previous(); !p.IsCode() || p.IsEphemeral() {
19 panic(ErrNotUserCall)
20 }
21}
22
23// assertSigner runs right after C11's branch: no session key, in either branch
24// (C11), then the caller must be who (API §3.6).
25func assertSigner(cur realm, who address) {
26 if _, _, _, isSession := runtime.GetSessionInfo(); isSession {
27 panic(ErrSessionKey)
28 }
29 if cur.Previous().Address() != who {
30 panic(ErrNotAdmin)
31 }
32}
33
34// ProposeAdmin proposes next as the admin, who takes over by accepting. None of
35// Bolo's own addresses can ever accept, so none can be proposed. A second proposal
36// replaces the first.
37func ProposeAdmin(cur realm, next address) {
38 assertCurrent(cur)
39 if cur.Previous().IsUserCall() {
40 runtime.AssertOriginCall()
41 } else {
42 assertCodeCaller(cur)
43 }
44 assertSigner(cur, admin)
45 checkOutsideBolo(next)
46 pendingAdmin = next
47 chain.Emit(evAdminProposed, "next", next.String())
48}
49
50// AcceptAdmin makes the proposed admin the admin, and ends the proposal.
51func AcceptAdmin(cur realm) {
52 assertCurrent(cur)
53 if cur.Previous().IsUserCall() {
54 runtime.AssertOriginCall()
55 } else {
56 assertCodeCaller(cur)
57 }
58 assertSigner(cur, pendingAdmin)
59 previous := admin
60 admin, pendingAdmin = pendingAdmin, ""
61 chain.Emit(evAdminChanged, "admin", admin.String(), "previous", previous.String())
62}
63
64// SetParams sets the Params new campaigns get; every live campaign keeps its own
65// (INV-5).
66func SetParams(cur realm, deposit int64, dormancyDays, exitDelayDays, bondHoldDays int) {
67 assertCurrent(cur)
68 if cur.Previous().IsUserCall() {
69 runtime.AssertOriginCall()
70 } else {
71 assertCodeCaller(cur)
72 }
73 assertSigner(cur, admin)
74 params = checkParams(deposit, dormancyDays, exitDelayDays, bondHoldDays)
75 chain.Emit(evParamsChanged,
76 "deposit", itoa(params.CreationDeposit),
77 "dormancy", itoa(params.DormancyPeriod),
78 "exitDelay", itoa(params.ExitDelay),
79 "bondHold", itoa(params.BondHold),
80 )
81}
82
83// SetTipRecipient sets where tips and swept strays go: never one of Bolo's own
84// addresses.
85func SetTipRecipient(cur realm, addr address) {
86 assertCurrent(cur)
87 if cur.Previous().IsUserCall() {
88 runtime.AssertOriginCall()
89 } else {
90 assertCodeCaller(cur)
91 }
92 assertSigner(cur, admin)
93 checkOutsideBolo(addr)
94 tipRecipient = addr
95 chain.Emit(evTipRecipientChanged, "to", addr.String())
96}
97
98// SetMinDonation sets the smallest gift Donate accepts, in ugnot, for new gifts.
99func SetMinDonation(cur realm, amount int64) {
100 assertCurrent(cur)
101 if cur.Previous().IsUserCall() {
102 runtime.AssertOriginCall()
103 } else {
104 assertCodeCaller(cur)
105 }
106 assertSigner(cur, admin)
107 if amount <= 0 {
108 panic(ErrSetting)
109 }
110 minDonation = amount
111 chain.Emit(evMinDonationChanged, "amount", itoa(amount))
112}
113
114// SetPresets sets the four one-click amounts, in ugnot. Display only.
115func SetPresets(cur realm, a, b, c, d int64) {
116 assertCurrent(cur)
117 if cur.Previous().IsUserCall() {
118 runtime.AssertOriginCall()
119 } else {
120 assertCodeCaller(cur)
121 }
122 assertSigner(cur, admin)
123 presets = checkPresets(a, b, c, d)
124 chain.Emit(evPresetsChanged, "a", itoa(a), "b", itoa(b), "c", itoa(c), "d", itoa(d))
125}
126
127// SetEscrowCap sets the most one campaign's tracked escrow balance may reach; 0
128// removes the cap. It gates gifts only, never money going out (INV-4).
129func SetEscrowCap(cur realm, max int64) {
130 assertCurrent(cur)
131 if cur.Previous().IsUserCall() {
132 runtime.AssertOriginCall()
133 } else {
134 assertCodeCaller(cur)
135 }
136 assertSigner(cur, admin)
137 if max < 0 {
138 panic(ErrSetting)
139 }
140 escrowCap = max
141 chain.Emit(evEscrowCapChanged, "max", itoa(max))
142}
143
144// AddCategory adds a category. There is no remove and no rename: every campaign
145// keeps a valid category.
146func AddCategory(cur realm, code string) {
147 assertCurrent(cur)
148 if cur.Previous().IsUserCall() {
149 runtime.AssertOriginCall()
150 } else {
151 assertCodeCaller(cur)
152 }
153 assertSigner(cur, admin)
154 checkNewCategory(code)
155 categories.Set(code, true)
156 categoryOrder = append(categoryOrder, code)
157 chain.Emit(evCategoryAdded, "code", code)
158}
159
160// Sunset stops CreateCampaign for good. It names no successor: where new campaigns
161// start is the registry's Current() (API §3.7). A second call changes nothing.
162func Sunset(cur realm) {
163 assertCurrent(cur)
164 if cur.Previous().IsUserCall() {
165 runtime.AssertOriginCall()
166 } else {
167 assertCodeCaller(cur)
168 }
169 assertSigner(cur, admin)
170 if sunset {
171 return
172 }
173 sunset = true
174 chain.Emit(evSunset)
175}
176
177// Pause stops CreateCampaign, Donate and PostBond — money coming in, never money
178// going out. Pausing while paused changes nothing.
179func Pause(cur realm) {
180 assertCurrent(cur)
181 if cur.Previous().IsUserCall() {
182 runtime.AssertOriginCall()
183 } else {
184 assertCodeCaller(cur)
185 }
186 assertSigner(cur, admin)
187 if paused {
188 return
189 }
190 paused = true
191 chain.Emit(evPaused)
192}
193
194// Unpause lifts a pause. Unpausing while running changes nothing.
195func Unpause(cur realm) {
196 assertCurrent(cur)
197 if cur.Previous().IsUserCall() {
198 runtime.AssertOriginCall()
199 } else {
200 assertCodeCaller(cur)
201 }
202 assertSigner(cur, admin)
203 if !paused {
204 return
205 }
206 paused = false
207 chain.Emit(evUnpaused)
208}
209
210// checkParams returns the Params for SetParams' arguments, or panics with
211// ErrSetting when one is out of bounds (API §3.6). The upper bounds keep the
212// conversion to seconds far from overflowing.
213func checkParams(deposit int64, dormancyDays, exitDelayDays, bondHoldDays int) Params {
214 if deposit <= 0 || !between(dormancyDays, 1, 3650) || !between(exitDelayDays, 7, 3650) || !between(bondHoldDays, 0, 3650) {
215 panic(ErrSetting)
216 }
217 return Params{
218 CreationDeposit: deposit,
219 DormancyPeriod: daysToSeconds(dormancyDays, ErrSetting),
220 ExitDelay: daysToSeconds(exitDelayDays, ErrSetting),
221 BondHold: daysToSeconds(bondHoldDays, ErrSetting),
222 }
223}
224
225func between(n, min, max int) bool { return n >= min && n <= max }
226
227// checkPresets returns the four amounts, or panics with ErrSetting unless they
228// are positive and strictly ascending (API §3.6).
229func checkPresets(a, b, c, d int64) [4]int64 {
230 if a <= 0 || a >= b || b >= c || c >= d {
231 panic(ErrSetting)
232 }
233 return [4]int64{a, b, c, d}
234}
235
236// checkOutsideBolo refuses, with ErrSetting, a malformed address (C12) and every
237// address of Bolo's own (API §3.6): the realm's, where tips would stay (INV-3); the
238// escrow and bond addresses derived so far, where an escrow would count tips as
239// untracked money; slashed, where they would be burned; the storage-deposit
240// address, the keeper's (Q8); and the registry, with its storage-deposit address.
241// Tips must not go there, and none of them can ever accept the admin role.
242func checkOutsideBolo(a address) {
243 checkAddress(a, ErrSetting)
244 if a == realmAddr() || a == slashedAddr() || a == depositAddr() || a == registryAddr() || a == registryDepositAddr() || derived.Has(a.String()) {
245 panic(ErrSetting)
246 }
247}
248
249// checkNewCategory refuses a code that isn't 3 to 20 lowercase letters, one already
250// listed, or one past the cap (API §3.6).
251func checkNewCategory(code string) {
252 if len(code) < 3 || len(code) > 20 || categories.Has(code) || len(categoryOrder) >= maxCategories {
253 panic(ErrSetting)
254 }
255 for i := 0; i < len(code); i++ {
256 if code[i] < 'a' || code[i] > 'z' {
257 panic(ErrSetting)
258 }
259 }
260}