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.

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}