moderation.gno
4.36 Kb · 141 lines
1package bolo
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime"
7 "strconv"
8)
9
10// Moderation (API §3.5): visibility, and the organizer's own bond — nothing else.
11// Every write here starts with C11's realm branch, then assertSigner: the master key,
12// then the admin's address, before anything is read. Then the campaign and its passed
13// deadline, since a slash's window runs from closedAt. The rules live in plain
14// helpers, which unit tests drive directly; the writes keep the caller checks, the
15// send and the events. Withdraw, Refund and Finalize never read what is written here
16// (INV-4).
17
18// Delist takes the campaign off the active lists, with a public notice, and with slash
19// moves its bond to the slashed address, where nothing ever spends it (INV-8).
20// Delisting again replaces the notice, and may slash a bond not yet slashed.
21func Delist(cur realm, id, notice string, slash bool) {
22 assertCurrent(cur)
23 if cur.Previous().IsUserCall() {
24 runtime.AssertOriginCall()
25 } else {
26 assertCodeCaller(cur)
27 }
28 assertSigner(cur, admin)
29 c := campaignOf(id)
30 recordDeadline(cur, c)
31 notice = checkText(notice, noticeMin, noticeMax, false, ErrNotice)
32 if slash {
33 bond := bookSlash(c)
34 banker.NewBanker(banker.BankerTypeRealmSend, bondSub(cur, c)).SendCoins(bondAddr(c.id), slashedAddr(), chain.NewCoins(chain.NewCoin("ugnot", bond)))
35 }
36 s := c.state
37 s.listed, s.notice = false, notice
38 dropActive(c)
39 chain.Emit(evDelisted, "id", idString(c.id), "notice", notice, "slashed", strconv.FormatBool(slash))
40}
41
42// bookSlash checks a slash of c's bond and enters it in the books, before the coins
43// move (API §3.5), and returns what moves: the bond, never the deposit. It refuses, in
44// order, no bond posted, a bond already slashed, a hold that has ended — once closed,
45// BondHold after closedAt, settled or not — and a bond address holding less than the
46// bond (INV-7).
47func bookSlash(c *campaign) int64 {
48 s := c.state
49 if s.bond == 0 {
50 panic(ErrNoBond)
51 }
52 if s.bondSlashed {
53 panic(ErrBondSlashed)
54 }
55 if s.status != statusOpen && now() >= s.closedAt+c.params.BondHold {
56 panic(ErrHoldEnded)
57 }
58 if banker.NewReadonlyBanker().GetCoin(bondAddr(c.id), "ugnot") < s.bond {
59 panic(ErrBooksMismatch)
60 }
61 s.bondSlashed = true
62 return s.bond
63}
64
65// Relist puts a delisted campaign back on the lists, and into the active indexes if
66// it is still open (API §3.5). Its notice stays, as does a slash. Relisting a listed
67// campaign changes nothing.
68func Relist(cur realm, id string) {
69 assertCurrent(cur)
70 if cur.Previous().IsUserCall() {
71 runtime.AssertOriginCall()
72 } else {
73 assertCodeCaller(cur)
74 }
75 assertSigner(cur, admin)
76 c := campaignOf(id)
77 recordDeadline(cur, c)
78 s := c.state
79 if s.listed {
80 return
81 }
82 s.listed = true
83 if s.status == statusOpen {
84 addActive(c)
85 }
86 chain.Emit(evRelisted, "id", idString(c.id))
87}
88
89// SetNotice attaches a public warning to the campaign without delisting it, or clears
90// it with "" (API §3.5). An unchanged notice changes nothing.
91func SetNotice(cur realm, id, notice string) {
92 assertCurrent(cur)
93 if cur.Previous().IsUserCall() {
94 runtime.AssertOriginCall()
95 } else {
96 assertCodeCaller(cur)
97 }
98 assertSigner(cur, admin)
99 c := campaignOf(id)
100 recordDeadline(cur, c)
101 notice = checkNotice(c, notice)
102 if notice == c.state.notice {
103 return
104 }
105 c.state.notice = notice
106 chain.Emit(evNoticeSet, "id", idString(c.id), "notice", notice)
107}
108
109// checkNotice returns notice as SetNotice stores it (API §3.5): "" clears, except on a
110// delisted campaign, which keeps a public reason; any other text is normalized, 1–280
111// characters. It panics with ErrNotice otherwise.
112func checkNotice(c *campaign, notice string) string {
113 if notice != "" {
114 return checkText(notice, noticeMin, noticeMax, false, ErrNotice)
115 }
116 if !c.state.listed {
117 panic(ErrNotice)
118 }
119 return ""
120}
121
122// HideMessage hides one gift's message (API §3.5): visibility only. The receipt, its
123// amount and its donor stay, and so does the stored text. Hiding a hidden message, or
124// a receipt without one, changes nothing.
125func HideMessage(cur realm, id string, seq int64) {
126 assertCurrent(cur)
127 if cur.Previous().IsUserCall() {
128 runtime.AssertOriginCall()
129 } else {
130 assertCodeCaller(cur)
131 }
132 assertSigner(cur, admin)
133 c := campaignOf(id)
134 recordDeadline(cur, c)
135 r := receiptOf(c, seq)
136 if r.message == "" || r.messageHidden {
137 return
138 }
139 r.messageHidden = true
140 chain.Emit(evMessageHidden, "id", idString(c.id), "seq", itoa(seq))
141}