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.

boardmod.gno

11.92 Kb · 421 lines
  1package kourtv3
  2import "strconv"
  3const (
  4	freezeMaxBlocks = int64(17_280) / 2
  5	freezeGapBlocks = freezeMaxBlocks
  6)
  7func mustBoardModInvariants() {
  8	if freezeMaxBlocks >= 17_280 {
  9		panic("kourtv3: a board freeze must expire inside the nomination window")
 10	}
 11	if freezeGapBlocks < freezeMaxBlocks {
 12		panic("kourtv3: the gap between freezes must be at least one freeze long, " +
 13			"or the ceiling is a formality")
 14	}
 15}
 16func init() { mustBoardModInvariants() }
 17func speechM(cm *courtMod) int {
 18	m := cm.m
 19	if m < 2 {
 20		m = 2
 21	}
 22	if m > cm.n {
 23		m = cm.n
 24	}
 25	return m
 26}
 27func SpeechThreshold(courtSlug string) int {
 28	c := mustCourt(courtSlug)
 29	if c.mod == nil {
 30		return 0
 31	}
 32	return speechM(c.mod)
 33}
 34func boardSanctionEpochOf(c *Court) int64 {
 35	if c.mod == nil {
 36		return 0
 37	}
 38	return c.mod.boardSanctionEpoch
 39}
 40func boardFreezeActive(r *standingRow) bool {
 41	passed, known := pastDeadline(r.frozenUntilTime, 0)
 42	return (known && !passed) || (!known && heightNow() < r.frozenUntil)
 43}
 44func boardFreezeGapOpen(r *standingRow) bool {
 45	passed, known := pastDeadline(r.frozenGapUntilTime, 0)
 46	return (known && !passed) || (!known && heightNow() < r.frozenGapUntil)
 47}
 48func boardFrozen(c *Court, who address) bool {
 49	r := lookupStanding(c, who)
 50	if r == nil || r.frozenUntil == 0 {
 51		return false
 52	}
 53	if r.frozenEpoch != boardSanctionEpochOf(c) {
 54		return false
 55	}
 56	return boardFreezeActive(r)
 57}
 58func boardFrozenAt(c *Court, who address) (at, atHeight int64) {
 59	if !boardFrozen(c, who) {
 60		return 0, 0
 61	}
 62	r := lookupStanding(c, who)
 63	return r.frozenUntilTime, r.frozenUntil
 64}
 65func BoardFrozenUntil(courtSlug string, who address) (at, atHeight int64) {
 66	return boardFrozenAt(mustCourt(courtSlug), who)
 67}
 68func claimFreezeActive(clm *claimMod) bool {
 69	passed, known := pastDeadline(clm.boardFrozenUntilTime, 0)
 70	return (known && !passed) || (!known && heightNow() < clm.boardFrozenUntil)
 71}
 72func claimFreezeGapOpen(clm *claimMod) bool {
 73	passed, known := pastDeadline(clm.boardFrozenGapUntilTime, 0)
 74	return (known && !passed) || (!known && heightNow() < clm.boardFrozenGapUntil)
 75}
 76func claimBoardFrozen(c *Court, claimID uint64) bool {
 77	if c.mod == nil {
 78		return false
 79	}
 80	clm := lookupClaimMod(c.mod, claimID)
 81	if clm == nil || clm.boardFrozenUntil == 0 {
 82		return false
 83	}
 84	if clm.boardFrozenEpoch != c.mod.boardSanctionEpoch {
 85		return false
 86	}
 87	return claimFreezeActive(clm)
 88}
 89func claimBoardFrozenAt(c *Court, claimID uint64) (at, atHeight int64) {
 90	if !claimBoardFrozen(c, claimID) {
 91		return 0, 0
 92	}
 93	clm := lookupClaimMod(c.mod, claimID)
 94	return clm.boardFrozenUntilTime, clm.boardFrozenUntil
 95}
 96func ClaimBoardFrozenUntil(courtSlug string, claimID uint64) (at, atHeight int64) {
 97	return claimBoardFrozenAt(mustCourt(courtSlug), claimID)
 98}
 99func HideBoardRow(cur realm, courtSlug string, claimID, rowID uint64, code string) {
100	if !cur.IsCurrent() {
101		panic(errStaleRealm)
102	}
103	who := cur.Previous().Address()
104	c := mustCourt(courtSlug)
105	cm := ensureMod(c)
106	requireActiveMod(cm, who)
107	mustBoardCode(code)
108	cs := mustClaim(c, claimID)
109	r := mustBoardRow(cs, rowID)
110	if r.hiddenByMod {
111		panic("kourtv3: that comment is already hidden")
112	}
113	fire, _ := approveAction(cm.pending,
114		"boardhide:"+strconv.FormatUint(claimID, 10)+":"+strconv.FormatUint(rowID, 10),
115		who, code, speechM(cm))
116	if !fire {
117		return
118	}
119	r.hiddenByMod = true
120	scoreIndexDrop(cs, r)
121	ensureClaimMod(c, cm, claimID).
122		appendLog(who, boardActCode("board-hide", rowID, code), "")
123	emitModAct(c.id, claimID, "board-hide", who)
124}
125func UnhideBoardRow(cur realm, courtSlug string, claimID, rowID uint64) {
126	if !cur.IsCurrent() {
127		panic(errStaleRealm)
128	}
129	who := cur.Previous().Address()
130	c := mustCourt(courtSlug)
131	cm := ensureMod(c)
132	requireMod(cm, who)
133	cs := mustClaim(c, claimID)
134	r := mustBoardRow(cs, rowID)
135	if !r.hiddenByMod {
136		panic("kourtv3: that comment is not hidden")
137	}
138	fire, _ := approveAction(cm.pending,
139		"boardunhide:"+strconv.FormatUint(claimID, 10)+":"+strconv.FormatUint(rowID, 10),
140		who, "", speechM(cm))
141	if !fire {
142		return
143	}
144	r.hiddenByMod = false
145	scoreIndexPut(cs, r)
146	ensureClaimMod(c, cm, claimID).
147		appendLog(who, boardActCode("board-unhide", rowID, ""), "")
148	emitModAct(c.id, claimID, "board-unhide", who)
149}
150func SlashStanding(cur realm, courtSlug string, who address, levels int64, code string) {
151	if !cur.IsCurrent() {
152		panic(errStaleRealm)
153	}
154	actor := cur.Previous().Address()
155	if levels < 1 || levels > 3 {
156		panic("kourtv3: a slash is 1, 2 or 3 whole levels")
157	}
158	c := mustCourt(courtSlug)
159	cm := ensureMod(c)
160	requireActiveMod(cm, actor)
161	mustBoardCode(code)
162	if who == "" {
163		panic("kourtv3: no such address")
164	}
165	fire, voted := approveAction(cm.pending,
166		"slash:"+who.String()+":"+strconv.FormatInt(levels, 10),
167		actor, code, speechM(cm))
168	if !fire {
169		return
170	}
171	r := getStanding(c, who)
172	target := postLevel(c, who) - levels
173	if target < 0 {
174		target = 0
175	}
176	if want := levelFloor(c, target); r.score > want {
177		r.score = want
178	}
179	if target == 0 {
180		r.passHeld = false
181	}
182	cm.appendCourtLog(actor, "slash:"+strconv.FormatInt(levels, 10), voted)
183	emitModAct(c.id, 0, "slash", actor)
184}
185func levelFloor(c *Court, lv int64) int64 {
186	if lv <= 0 {
187		return 0
188	}
189	l := ladderFor(c)
190	sup, _ := boardSupply(c)
191	switch lv {
192	case 1:
193		return sup * l.t1Bps / 10_000
194	case 2:
195		return sup * l.t2Bps / 10_000
196	}
197	return sup * l.t3Bps / 10_000
198}
199func RestoreStanding(cur realm, courtSlug string, who address) int64 {
200	if !cur.IsCurrent() {
201		panic(errStaleRealm)
202	}
203	actor := cur.Previous().Address()
204	c := mustCourt(courtSlug)
205	cm := ensureMod(c)
206	requireMod(cm, actor)
207	r := lookupStanding(c, who)
208	if r == nil || r.score >= r.highWater {
209		panic("kourtv3: that address has nothing to restore")
210	}
211	fire, _ := approveAction(cm.pending, "restore:"+who.String(), actor, "",
212		speechM(cm))
213	if !fire {
214		return 0
215	}
216	r.score = r.highWater
217	cm.appendCourtLog(actor, "restore", "")
218	emitModAct(c.id, 0, "restore", actor)
219	return r.score
220}
221func FreezeBoard(cur realm, courtSlug string, who address, blocks int64, code string) {
222	if !cur.IsCurrent() {
223		panic(errStaleRealm)
224	}
225	actor := cur.Previous().Address()
226	if blocks < 1 || blocks > freezeMaxBlocks {
227		panic("kourtv3: a board freeze is at most freezeMaxBlocks long")
228	}
229	c := mustCourt(courtSlug)
230	cm := ensureMod(c)
231	requireActiveMod(cm, actor)
232	mustBoardCode(code)
233	if who == "" {
234		panic("kourtv3: no such address")
235	}
236	now := heightNow()
237	if r := lookupStanding(c, who); r != nil && boardFreezeGapOpen(r) {
238		panic("kourtv3: this address was frozen too recently; the ceiling is not a formality")
239	}
240	fire, voted := approveAction(cm.pending, "freeze:"+who.String(), actor, code,
241		speechM(cm))
242	if !fire {
243		return
244	}
245	r := getStanding(c, who)
246	r.frozenUntil = now + blocks
247	r.frozenUntilTime = nowTime() + blocksToSecs(blocks)
248	r.frozenEpoch = boardSanctionEpochOf(c)
249	r.frozenGapUntil = now + freezeMaxBlocks + freezeGapBlocks
250	r.frozenGapUntilTime = nowTime() + blocksToSecs(freezeMaxBlocks+freezeGapBlocks)
251	cm.appendCourtLog(actor, "freeze:"+strconv.FormatInt(blocks, 10), voted)
252	emitModAct(c.id, 0, "freeze", actor)
253}
254func UnfreezeBoard(cur realm, courtSlug string, who address) {
255	if !cur.IsCurrent() {
256		panic(errStaleRealm)
257	}
258	actor := cur.Previous().Address()
259	c := mustCourt(courtSlug)
260	cm := ensureMod(c)
261	requireMod(cm, actor)
262	if !boardFrozen(c, who) {
263		panic("kourtv3: that address is not frozen")
264	}
265	fire, _ := approveAction(cm.pending, "unfreeze:"+who.String(), actor, "",
266		speechM(cm))
267	if !fire {
268		return
269	}
270	lifted := lookupStanding(c, who)
271	lifted.frozenUntil = 0
272	lifted.frozenUntilTime = 0
273	cm.appendCourtLog(actor, "unfreeze", "")
274	emitModAct(c.id, 0, "unfreeze", actor)
275}
276func FreezeClaimBoard(cur realm, courtSlug string, claimID uint64, blocks int64, code string) {
277	if !cur.IsCurrent() {
278		panic(errStaleRealm)
279	}
280	actor := cur.Previous().Address()
281	if blocks < 1 || blocks > freezeMaxBlocks {
282		panic("kourtv3: a board freeze is at most freezeMaxBlocks long")
283	}
284	c := mustCourt(courtSlug)
285	cm := ensureMod(c)
286	requireActiveMod(cm, actor)
287	mustBoardCode(code)
288	_ = mustClaim(c, claimID)
289	now := heightNow()
290	if prev := lookupClaimMod(cm, claimID); prev != nil && claimFreezeGapOpen(prev) {
291		panic("kourtv3: this board was frozen too recently; the ceiling is not a formality")
292	}
293	fire, _ := approveAction(cm.pending,
294		"freezeboard:"+strconv.FormatUint(claimID, 10), actor, code, speechM(cm))
295	if !fire {
296		return
297	}
298	clm := ensureClaimMod(c, cm, claimID)
299	clm.boardFrozenUntil = now + blocks
300	clm.boardFrozenUntilTime = nowTime() + blocksToSecs(blocks)
301	clm.boardFrozenEpoch = cm.boardSanctionEpoch
302	clm.boardFrozenGapUntil = now + freezeMaxBlocks + freezeGapBlocks
303	clm.boardFrozenGapUntilTime = nowTime() + blocksToSecs(freezeMaxBlocks+freezeGapBlocks)
304	clm.appendLog(actor, "freezeboard:"+strconv.FormatInt(blocks, 10)+":"+code, "")
305	emitModAct(c.id, claimID, "freezeboard", actor)
306}
307func ThawClaimBoard(cur realm, courtSlug string, claimID uint64) {
308	if !cur.IsCurrent() {
309		panic(errStaleRealm)
310	}
311	actor := cur.Previous().Address()
312	c := mustCourt(courtSlug)
313	cm := ensureMod(c)
314	requireMod(cm, actor)
315	if !claimBoardFrozen(c, claimID) {
316		panic("kourtv3: that board is not frozen")
317	}
318	fire, _ := approveAction(cm.pending,
319		"thawboard:"+strconv.FormatUint(claimID, 10), actor, "", speechM(cm))
320	if !fire {
321		return
322	}
323	thawed := lookupClaimMod(cm, claimID)
324	thawed.boardFrozenUntil = 0
325	thawed.boardFrozenUntilTime = 0
326	ensureClaimMod(c, cm, claimID).appendLog(actor, "thawboard", "")
327	emitModAct(c.id, claimID, "thawboard", actor)
328}
329func GlobalClearBoardBits(cur realm, courtSlug string) int64 {
330	if !cur.IsCurrent() {
331		panic(errStaleRealm)
332	}
333	who := cur.Previous().Address()
334	if !isGlobalMod(who) {
335		panic("kourtv3: only a global DAO member may clear board bits (recovery)")
336	}
337	c := mustCourt(courtSlug)
338	cm := ensureMod(c)
339	cm.boardSanctionEpoch++
340	cm.appendCourtLog(who, "global-clear-board-bits", "")
341	emitGlobalAct(c.id, 0, "clear-board-bits", who)
342	return cm.boardSanctionEpoch
343}
344func GlobalRestoreStanding(cur realm, courtSlug string, who address) int64 {
345	if !cur.IsCurrent() {
346		panic(errStaleRealm)
347	}
348	actor := cur.Previous().Address()
349	if !isGlobalMod(actor) {
350		panic("kourtv3: only a global DAO member may restore standing (recovery)")
351	}
352	c := mustCourt(courtSlug)
353	r := lookupStanding(c, who)
354	if r == nil || r.score >= r.highWater {
355		panic("kourtv3: that address has nothing to restore")
356	}
357	r.score = r.highWater
358	ensureMod(c).appendCourtLog(actor, "global-restore-standing", "")
359	emitGlobalAct(c.id, 0, "restore-standing", actor)
360	return r.score
361}
362func mustBoardWritable(c *Court, cs *claimState, who address) {
363	if claimBoardFrozen(c, cs.id) {
364		panic("kourtv3: this claim's board is frozen by moderators")
365	}
366	if boardFrozen(c, who) {
367		panic("kourtv3: you are frozen out of this court's boards")
368	}
369}
370func PurgeCourtLogRow(cur realm, courtSlug string, rowID uint64, categoryCode string) {
371	if !cur.IsCurrent() {
372		panic(errStaleRealm)
373	}
374	who := cur.Previous().Address()
375	d := ensureGlobalDAO()
376	if !d.members.Has(who.String()) {
377		panic("kourtv3: only a global DAO member may purge")
378	}
379	mustCategoryCode(categoryCode)
380	c := mustCourt(courtSlug)
381	cm := ensureMod(c)
382	idx := -1
383	for i := range cm.log {
384		if cm.log[i].rowID == rowID {
385			idx = i
386			break
387		}
388	}
389	if idx < 0 {
390		panic("kourtv3: no such court log row")
391	}
392	if cm.log[idx].purged {
393		return
394	}
395	fire, code := approveAction(d.pending,
396		"purgecourtrow:"+c.id+":"+strconv.FormatUint(rowID, 10),
397		who, categoryCode, d.purgeM)
398	if !fire {
399		return
400	}
401	cm.log[idx].purged = true
402	cm.log[idx].reason = ""
403	cm.appendCourtLog(who, "purgecourtrow:"+code, "")
404	emitPurge(c.id, 0, code, who)
405}
406func GlobalClearCourtParams(cur realm, courtSlug string) {
407	if !cur.IsCurrent() {
408		panic(errStaleRealm)
409	}
410	who := cur.Previous().Address()
411	if !isGlobalMod(who) {
412		panic("kourtv3: only a global DAO member may clear court parameters (recovery)")
413	}
414	c := mustCourt(courtSlug)
415	if c.ladder == nil && c.creditRates == nil {
416		panic("kourtv3: this court has no parameter overrides")
417	}
418	c.ladder, c.creditRates = nil, nil
419	ensureMod(c).appendCourtLog(who, "global-clear-params", "")
420	emitGlobalAct(c.id, 0, "clear-court-params", who)
421}