meta.gno
9.87 Kb · 380 lines
1package kourt
2import (
3 "chain"
4 "strconv"
5 "strings"
6 unsafe "chain/runtime/unsafe"
7 "math/overflow"
8 bptree "gno.land/p/nt/bptree/v0"
9)
10const metaRealmPath = "gno.land/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt"
11const (
12 metaPrefix = "mod:"
13 metaCourtNm = "Meta"
14 metaExecuted = "MetaExecuted"
15)
16const (
17 verbUnhide = "unhide"
18 verbClear = "clear"
19 verbHide = "hide"
20 verbSuspend = "suspend"
21 verbUnsuspend = "unsuspend"
22 verbSetmods = "setmods"
23 verbNewset = "newset"
24)
25func verbIsAggressive(v string) bool {
26 return v == verbHide || v == verbSuspend || v == verbUnsuspend || v == verbSetmods
27}
28type modParse struct {
29 valid bool
30 verb string
31 court string
32 target uint64
33 hasTarget bool
34 poisoned bool
35}
36type metaState struct {
37 parses *bptree.BPTree
38 bindings *bptree.BPTree
39 latches *bptree.BPTree
40 executed *bptree.BPTree
41}
42var meta = &metaState{
43 parses: bptree.NewBPTree32(),
44 bindings: bptree.NewBPTree32(),
45 latches: bptree.NewBPTree32(),
46 executed: bptree.NewBPTree32(),
47}
48func init() {
49 startCourt(unsafe.OriginCaller(), chain.PackageAddress(metaRealmPath), metaSlug, metaCourtNm, 0)
50}
51func parseModTitle(title string) modParse {
52 p := modParse{}
53 if !strings.HasPrefix(title, metaPrefix) {
54 return p
55 }
56 rest := title[len(metaPrefix):]
57 slash := strings.Index(rest, ":")
58 if slash < 0 {
59 return p
60 }
61 verb := rest[:slash]
62 arg := rest[slash+1:]
63 switch verb {
64 case verbUnhide, verbClear, verbHide, verbSetmods, verbNewset:
65 cut := strings.Index(arg, "/")
66 if cut <= 0 || cut == len(arg)-1 {
67 return p
68 }
69 id, err := strconv.ParseUint(arg[cut+1:], 10, 64)
70 if err != nil || id == 0 {
71 return p
72 }
73 p.court, p.target, p.hasTarget = arg[:cut], id, true
74 case verbSuspend, verbUnsuspend:
75 if arg == "" || strings.Contains(arg, "/") {
76 return p
77 }
78 p.court = arg
79 default:
80 return p
81 }
82 for i := 0; i < len(p.court); i++ {
83 ch := p.court[i]
84 if !((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-') {
85 return modParse{}
86 }
87 }
88 p.verb, p.valid = verb, true
89 return p
90}
91func latchKey(p modParse) string {
92 switch p.verb {
93 case verbSetmods:
94 return "set:" + p.court
95 case verbNewset:
96 return "fold:" + p.court
97 case verbSuspend, verbUnsuspend:
98 return "sus:" + p.court
99 default:
100 return "item:" + p.court + ":" + strconv.FormatUint(p.target, 10)
101 }
102}
103func onClaimOpened(c *Court, cs *claimState) {
104 if c.id != metaSlug {
105 return
106 }
107 p := parseModTitle(cs.title)
108 meta.parses.Set(beClaimKey(cs.id), &p)
109 cs.noAccuracy = p.valid && p.verb == verbNewset
110 if p.valid {
111 pendingEnter(c, cs)
112 }
113}
114func onTitleEdited(c *Court, cs *claimState) {
115 if c.id != metaSlug {
116 return
117 }
118 p := parseModTitle(cs.title)
119 if prev := meta.parses.Get(beClaimKey(cs.id)); prev != nil && prev.(*modParse).poisoned {
120 p.poisoned = true
121 }
122 meta.parses.Set(beClaimKey(cs.id), &p)
123 cs.noAccuracy = p.valid && p.verb == verbNewset
124 if p.valid || cs.seeded {
125 pendingEnter(c, cs)
126 } else {
127 pendingExit(c, cs)
128 }
129}
130func onClaimAnswered(c *Court, cs *claimState) {
131 if c.id != metaSlug {
132 return
133 }
134 v := meta.parses.Get(beClaimKey(cs.id))
135 if v == nil {
136 return
137 }
138 p := v.(*modParse)
139 if !p.valid || p.poisoned {
140 return
141 }
142 k := latchKey(*p)
143 if held := meta.latches.Get(k); held != nil {
144 if held.(uint64) != cs.id {
145 panic("kourtv2: another appeal already holds this target's review slot")
146 }
147 }
148 meta.latches.Set(k, cs.id)
149 bind := *p
150 meta.bindings.Set(beClaimKey(cs.id), &bind)
151}
152func releaseMetaLatchIfMeta(c *Court, cs *claimState) {
153 if c.id != metaSlug {
154 return
155 }
156 releaseMetaLatch(cs)
157}
158func releaseMetaLatch(cs *claimState) {
159 v := meta.bindings.Get(beClaimKey(cs.id))
160 if v == nil {
161 return
162 }
163 k := latchKey(*v.(*modParse))
164 if held := meta.latches.Get(k); held != nil && held.(uint64) == cs.id {
165 meta.latches.Remove(k)
166 }
167}
168func poisonMetaParse(claimID uint64) {
169 if v := meta.parses.Get(beClaimKey(claimID)); v != nil {
170 v.(*modParse).poisoned = true
171 }
172}
173func metaVerdictExpired(cs *claimState, votingBlocks int64) bool {
174 passed, known := pastDeadline(cs.verdictAtTime, blocksToSecs(votingBlocks)+1)
175 return (known && passed) || (!known && heightNow() > cs.verdictAt+votingBlocks)
176}
177func ExecuteMetaVerdict(cur realm, metaClaimID uint64) {
178 if !cur.IsCurrent() {
179 panic(errStaleRealm)
180 }
181 mc := mustCourt(metaSlug)
182 cs := mustClaim(mc, metaClaimID)
183 bv := meta.bindings.Get(beClaimKey(metaClaimID))
184 if bv == nil {
185 panic("kourtv2: this claim carries no binding appeal")
186 }
187 p := *bv.(*modParse)
188 if !Exists(p.court) {
189 panic("kourtv2: the target court does not exist")
190 }
191 tc := mustCourt(p.court)
192 if tc.createdAt >= cs.openedAt {
193 panic("kourtv2: the target court does not predate this appeal")
194 }
195 var target *claimState
196 if p.hasTarget && p.verb != verbSetmods && p.verb != verbNewset {
197 target = mustClaim(tc, p.target)
198 if target.openedAt >= cs.openedAt {
199 panic("kourtv2: the target claim does not predate this appeal")
200 }
201 }
202 if p.verb == verbSetmods {
203 if cand := ensureMod(tc).mustCandidate(p.target); cand.at >= cs.openedAt {
204 panic("kourtv2: the candidate set does not predate this appeal")
205 }
206 }
207 if p.verb == verbNewset {
208 if prop := ensureMod(tc).mustFolderProposal(p.target); prop.at >= cs.openedAt {
209 panic("kourtv2: the folder proposal does not predate this appeal")
210 }
211 }
212 if cs.verdictAt == 0 || cs.provClose {
213 panic("kourtv2: no final verdict")
214 }
215 if int(cs.provisional) != sideYES {
216 panic("kourtv2: the verdict was not YES")
217 }
218 if verbIsAggressive(p.verb) {
219 if cs.route != "vote" || cs.decidedRounds == 0 {
220 panic("kourtv2: this verb needs a decided contested vote, not silence")
221 }
222 if p.verb != verbHide && !cs.credEligible {
223 panic("kourtv2: a manufactured contest cannot seize authority — this verb needs weight voted against it")
224 }
225 }
226 if meta.executed.Has(beClaimKey(metaClaimID)) {
227 panic("kourtv2: already executed")
228 }
229 if metaVerdictExpired(cs, mc.params.votingBlocks) {
230 panic("kourtv2: this verdict has expired; re-appeal")
231 }
232 cm := ensureMod(tc)
233 switch p.verb {
234 case verbSetmods:
235 if cm.setActHeight > cs.verdictAt {
236 panic("kourtv2: the moderator set changed after this verdict; re-appeal")
237 }
238 if cm.suspended && cm.suspendActByGlobal > cs.openedAt {
239 panic("kourtv2: the global DAO changed this court's suspension under this appeal; re-appeal")
240 }
241 case verbSuspend, verbUnsuspend:
242 if cm.lastElectionAt > cs.openedAt || cm.setActHeight > cs.verdictAt {
243 panic("kourtv2: the moderator set changed under this appeal; re-appeal")
244 }
245 if cm.suspendActByGlobal > cs.openedAt &&
246 cm.suspended == (p.verb == verbUnsuspend) {
247 panic("kourtv2: the global DAO changed this court's suspension under this appeal; re-appeal")
248 }
249 case verbUnhide:
250 if cm.lastElectionAt > cs.openedAt {
251 panic("kourtv2: the moderator set changed under this appeal; re-appeal")
252 }
253 }
254 meta.executed.Set(beClaimKey(metaClaimID), true)
255 applyMetaVerb(mc, tc, cm, p, cs, target)
256 releaseMetaLatch(cs)
257 chain.Emit(metaExecuted,
258 "appeal", strconv.FormatUint(metaClaimID, 10),
259 "verb", p.verb, "court", p.court,
260 "target", strconv.FormatUint(p.target, 10),
261 )
262}
263func applyMetaVerb(mc, tc *Court, cm *courtMod, p modParse, cs, target *claimState) {
264 by := cs.answerer
265 switch p.verb {
266 case verbUnhide:
267 clm := lookupClaimMod(cm, p.target)
268 if clm == nil || !clm.court {
269 return
270 }
271 clearCourtBitByMeta(tc, p.target, by)
272 case verbClear:
273 clearMetaBit(tc, p.target, by)
274 case verbHide:
275 setMetaBit(tc, p.target, by)
276 case verbSuspend:
277 suspendSet(tc, by)
278 case verbUnsuspend:
279 unsuspendSet(tc, by)
280 case verbNewset:
281 prop := cm.mustFolderProposal(p.target)
282 if folderNameTaken(cm, prop.name) {
283 panic("kourtv2: a folder by that name already exists")
284 }
285 if prop.parent != 0 {
286 if pf := cm.mustFolder(prop.parent); pf.retired || pf.purged {
287 panic("kourtv2: the parent heading is no longer in the tree")
288 }
289 }
290 fid := newFolderCore(cm, prop.name, prop.desc, prop.parent)
291 emitModAct(tc.id, 0, "folder-create", by)
292 chain.Emit("FolderCreated",
293 "court", tc.id, "by", "meta",
294 "folder", strconv.FormatUint(fid, 10),
295 "proposal", strconv.FormatUint(p.target, 10),
296 "height", eventHeight(),
297 )
298 case verbSetmods:
299 cand := cm.mustCandidate(p.target)
300 installModSet(tc, cm, cand, heightNow(), false)
301 chain.Emit("ModSetInstalled",
302 "court", tc.id, "by", "meta",
303 "candidate", strconv.FormatUint(cand.id, 10),
304 "height", eventHeight(),
305 )
306 }
307}
308func AppealParse(claimID uint64) (verb, court string, target uint64, valid bool) {
309 v := meta.parses.Get(beClaimKey(claimID))
310 if v == nil {
311 return "", "", 0, false
312 }
313 p := v.(*modParse)
314 return p.verb, p.court, p.target, p.valid
315}
316func AppealExecuted(claimID uint64) bool { return meta.executed.Has(beClaimKey(claimID)) }
317func AppealLatchHolder(verb, court string, target uint64) uint64 {
318 p := modParse{verb: verb, court: court, target: target}
319 if v := meta.latches.Get(latchKey(p)); v != nil {
320 return v.(uint64)
321 }
322 return 0
323}
324var franchise = bptree.NewBPTree32()
325func accrueFranchise(c *Court, who address, spent int64) {
326 if c.id == metaSlug || spent <= 0 {
327 return
328 }
329 k := who.String()
330 prev := int64(0)
331 if v := franchise.Get(k); v != nil {
332 prev = v.(int64)
333 }
334 sum, ok := overflow.Add64(prev, spent)
335 if !ok {
336 return
337 }
338 franchise.Set(k, sum)
339}
340func ClaimMetaFranchise(cur realm) int64 {
341 if !cur.IsCurrent() {
342 panic(errStaleRealm)
343 }
344 who := cur.Previous().Address()
345 k := who.String()
346 v := franchise.Get(k)
347 if v == nil {
348 return 0
349 }
350 owed := v.(int64)
351 if owed <= 0 {
352 return 0
353 }
354 mc := mustCourt(metaSlug)
355 touch(mc)
356 delta, used := mc.crv.Minted(mc.minted, owed)
357 if delta <= 0 {
358 return 0
359 }
360 mc.minted += delta
361 recordPrice(mc)
362 mc.coin.Mint(who, delta)
363 if used >= owed {
364 franchise.Remove(k)
365 } else {
366 franchise.Set(k, owed-used)
367 }
368 chain.Emit("FranchiseClaimed",
369 "who", who.String(),
370 "burned", strconv.FormatInt(used, 10),
371 "minted", strconv.FormatInt(delta, 10),
372 )
373 return delta
374}
375func FranchiseOf(who address) int64 {
376 if v := franchise.Get(who.String()); v != nil {
377 return v.(int64)
378 }
379 return 0
380}