Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

claim.gno

10.28 Kb · 376 lines
  1package kourt
  2import (
  3	"chain"
  4	"strconv"
  5	"strings"
  6	checkpoint "gno.land/p/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/checkpoint/v0"
  7	twap "gno.land/p/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/twap/v0"
  8	bptree "gno.land/p/nt/bptree/v0"
  9)
 10const (
 11	sideYES = 0
 12	sideNO  = 1
 13)
 14const twapBuckets = 168
 15const maxClaimBodyLen = 2000
 16func runeLen(s string) int {
 17	n := 0
 18	for range s {
 19		n++
 20	}
 21	return n
 22}
 23type claimState struct {
 24	id     uint64
 25	title  string
 26	body   string
 27	media  []mediaItem
 28	author address
 29	deposit  int64
 30	fee      int64
 31	seeded   bool
 32	noAccuracy bool
 33	openedAt int64
 34	openedAtTime   int64
 35	answeredAtTime int64
 36	verdictAtTime  int64
 37	escrowUntilAt  int64
 38	closed         bool
 39	board      *bptree.BPTree
 40	boardTop   *bptree.BPTree
 41	boardKids  *bptree.BPTree
 42	boardWrote *bptree.BPTree
 43	boardScore *bptree.BPTree
 44	boardVoted *bptree.BPTree
 45	boardNextID uint64
 46	inStrip   bool
 47	inPending bool
 48	yesStake, noStake int64
 49	stakers           *bptree.BPTree
 50	yesConvHi, yesConvLo uint64
 51	noConvHi, noConvLo   uint64
 52	yesRawHi, yesRawLo   uint64
 53	noRawHi, noRawLo     uint64
 54	yesLastAcc, noLastAcc     int64
 55	yesLastRawAt, noLastRawAt int64
 56	oi  twap.Ring
 57	yes twap.Ring
 58	yesH, noH     checkpoint.Series
 59	yesD, noD     checkpoint.Series
 60	histTrimmedTo uint32
 61	frozenAt        int64
 62	rateAccAtFreeze int64
 63	xBarFrozen      int64
 64	yesStakeAtFreeze, noStakeAtFreeze int64
 65	answer       int8
 66	answerer     address
 67	answerBond   int64
 68	answerBond0  int64
 69	answerHeight int64
 70	verdictAt int64
 71	route     string
 72	tierRef int64
 73	disputeOpen   bool
 74	disputeOpenedTime int64
 75	round         int64
 76	failedRounds  int64
 77	decidedRounds int64
 78	disputer      address
 79	disputeBond   int64
 80	overturnBy address
 81	overturnBond int64
 82	proposalID    int64
 83	provisional   int8
 84	escrowUntil   int64
 85	provClose     bool
 86	credEligible bool
 87	voted          *bptree.BPTree
 88	rewardsOpened bool
 89	drawWinners   int64
 90	drawAuthor    int64
 91	drawAnswerer  int64
 92	winPoolConvCC int64
 93	openBlocks    int64
 94	carrotTotal   int64
 95	carrotPool    int64
 96	carrotChoice  string
 97	carrotDenom   int64
 98	decidedPID    int64
 99	spamW      int64
100	spamTotalW int64
101	spamClosed bool
102	authorPaid    bool
103	answererPaid  bool
104}
105func OpenClaim(cur realm, courtSlug, title string) uint64 {
106	if !cur.IsCurrent() {
107		panic(errStaleRealm)
108	}
109	c := mustCourt(courtSlug)
110	if courtIsPurged(c) {
111		panic("kourtv2: this court is purged; no new claims may be opened")
112	}
113	return openClaim(c, cur.Previous().Address(), title, "", false)
114}
115func OpenClaimIn(cur realm, courtSlug string, folderID uint64, title, body string) uint64 {
116	if !cur.IsCurrent() {
117		panic(errStaleRealm)
118	}
119	c := mustCourt(courtSlug)
120	if courtIsPurged(c) {
121		panic("kourtv2: this court is purged; no new claims may be opened")
122	}
123	cm := ensureMod(c)
124	f := cm.mustFolder(folderID)
125	if f.retired || f.purged {
126		panic("kourtv2: that set is retired; a claim filed there would be unreachable")
127	}
128	who := cur.Previous().Address()
129	id := openClaim(c, who, title, body, false)
130	fileInto(f, id)
131	emitModAct(c.id, id, "folder-add", who)
132	return id
133}
134func OpenClaimP(cur realm, courtSlug, title, body string) uint64 {
135	if !cur.IsCurrent() {
136		panic(errStaleRealm)
137	}
138	c := mustCourt(courtSlug)
139	if courtIsPurged(c) {
140		panic("kourtv2: this court is purged; no new claims may be opened")
141	}
142	return openClaim(c, cur.Previous().Address(), title, body, false)
143}
144func OpenClaimSeeded(cur realm, courtSlug, title string) uint64 {
145	if !cur.IsCurrent() {
146		panic(errStaleRealm)
147	}
148	who := cur.Previous().Address()
149	c := mustCourt(courtSlug)
150	if courtIsPurged(c) {
151		panic("kourtv2: this court is purged; no new claims may be opened")
152	}
153	cm := ensureMod(c)
154	requireActiveMod(cm, who)
155	if cm.installedByMeta {
156		panic("kourtv2: a meta-installed moderator set may not seed claims; seed after a local election")
157	}
158	return openClaim(c, who, title, "", true)
159}
160func OpenClaimPM(cur realm, courtSlug, title, body, media string) uint64 {
161	if !cur.IsCurrent() {
162		panic(errStaleRealm)
163	}
164	c := mustCourt(courtSlug)
165	if courtIsPurged(c) {
166		panic("kourtv2: this court is purged; no new claims may be opened")
167	}
168	items := parseMediaArg(media)
169	id := openClaim(c, cur.Previous().Address(), title, body, false)
170	if len(items) > 0 {
171		mustClaim(c, id).media = items
172	}
173	return id
174}
175func openClaim(c *Court, who address, title, body string, seeded bool) uint64 {
176	if runeLen(title) == 0 || runeLen(title) > 200 {
177		panic("kourtv2: a claim title is 1..200 characters")
178	}
179	if runeLen(body) > maxClaimBodyLen {
180		panic("kourtv2: a claim body is at most " + strconv.Itoa(maxClaimBodyLen) + " characters")
181	}
182	var dep, fee int64
183	if !seeded {
184		dep = effMinDeposit(c)
185		fee = dep * depositFeeBps / 10000
186		mustSpendable(c, who, dep+fee)
187		c.coin.Transfer(who, c.escrow, dep+fee)
188		c.deposit += dep + fee
189	}
190	c.nextID++
191	id := c.nextID
192	cs := &claimState{
193		id:           id,
194		title:        title,
195		body:         body,
196		author:       who,
197		deposit:      dep,
198		fee:          fee,
199		seeded:       seeded,
200		openedAt:     heightNow(),
201		openedAtTime: nowTime(),
202		stakers:      bptree.NewBPTree32(),
203		oi:           twap.New(epochBlocks, twapBuckets, 8),
204		yes:          twap.New(epochBlocks, twapBuckets, 8),
205		provisional:  -1,
206	}
207	c.claims.Set(beClaimKey(id), cs)
208	if seeded {
209		pendingEnter(c, cs)
210	}
211	onClaimOpened(c, cs)
212	chain.Emit("ClaimOpened",
213		"court", c.id,
214		"claim", strconv.FormatUint(id, 10),
215		"author", who.String(),
216		"seeded", strconv.FormatBool(seeded),
217	)
218	return id
219}
220func EditClaimTitle(cur realm, courtSlug string, claimID uint64, newTitle string) {
221	if !cur.IsCurrent() {
222		panic(errStaleRealm)
223	}
224	who := cur.Previous().Address()
225	c := mustCourt(courtSlug)
226	cs := mustClaim(c, claimID)
227	if who != cs.author {
228		panic("kourtv2: only the author may edit the title")
229	}
230	if len(newTitle) == 0 || len(newTitle) > 200 {
231		panic("kourtv2: a claim title is 1..200 characters")
232	}
233	if cs.closed || cs.frozenAt != 0 {
234		panic("kourtv2: the claim is no longer editable")
235	}
236	if passed, known := pastDeadline(cs.openedAtTime, blocksToSecs(c.params.stakeOpenDelayBlocks)); (known && passed) ||
237		(!known && heightNow() >= cs.openedAt+c.params.stakeOpenDelayBlocks) {
238		panic("kourtv2: the polish window has closed; the title is frozen")
239	}
240	if cs.yesStake != 0 || cs.noStake != 0 {
241		panic("kourtv2: staking has begun; the title is frozen")
242	}
243	cs.title = newTitle
244	onTitleEdited(c, cs)
245	chain.Emit("ClaimTitleEdited", "court", c.id, "claim", strconv.FormatUint(claimID, 10))
246}
247func CloseDeadClaim(cur realm, courtSlug string, claimID uint64) {
248	if !cur.IsCurrent() {
249		panic(errStaleRealm)
250	}
251	c := mustCourt(courtSlug)
252	cs := mustClaim(c, claimID)
253	if cs.closed {
254		panic("kourtv2: already closed")
255	}
256	if cs.frozenAt != 0 {
257		panic("kourtv2: this claim has an answer; it settles, it does not die")
258	}
259	if passed, known := pastDeadline(cs.openedAtTime, deadClaimSecs); (known && !passed) ||
260		(!known && heightNow() < cs.openedAt+deadClaimTimeout) {
261		panic("kourtv2: the dead-claim timeout has not passed")
262	}
263	cs.closed = true
264	pendingExit(c, cs)
265	stripExit(c, cs)
266	releaseMetaLatchIfMeta(c, cs)
267	if cs.deposit > 0 {
268		c.coin.Transfer(c.escrow, cs.author, cs.deposit)
269		c.deposit -= cs.deposit
270		cs.deposit = 0
271	}
272	if cs.fee > 0 {
273		c.coin.Burn(c.escrow, cs.fee)
274		c.deposit -= cs.fee
275		cs.fee = 0
276	}
277}
278func ClaimCount(courtSlug string) uint64 { return mustCourt(courtSlug).nextID }
279func ClaimTitle(courtSlug string, claimID uint64) string {
280	c := mustCourt(courtSlug)
281	return claimTitleFor(c, mustClaim(c, claimID))
282}
283func ClaimBody(courtSlug string, claimID uint64) string {
284	c := mustCourt(courtSlug)
285	return claimBodyFor(c, mustClaim(c, claimID))
286}
287func ClaimMedia(courtSlug string, claimID uint64) string {
288	c := mustCourt(courtSlug)
289	return encodeMedia(claimMediaVisible(c, mustClaim(c, claimID)))
290}
291func ClaimAuthor(courtSlug string, claimID uint64) address {
292	return mustClaim(mustCourt(courtSlug), claimID).author
293}
294func ClaimClosed(courtSlug string, claimID uint64) bool {
295	return mustClaim(mustCourt(courtSlug), claimID).closed
296}
297func ClaimSeeded(courtSlug string, claimID uint64) bool {
298	return mustClaim(mustCourt(courtSlug), claimID).seeded
299}
300func StakePools(courtSlug string, claimID uint64) (yes, no int64) {
301	cs := mustClaim(mustCourt(courtSlug), claimID)
302	return cs.yesStake, cs.noStake
303}
304func TrailingOI(courtSlug string, claimID uint64, window int64) (avg int64, mature bool) {
305	cs := mustClaim(mustCourt(courtSlug), claimID)
306	return cs.oi.Average(heightNow(), window)
307}
308func PoolConviction(courtSlug string, claimID uint64) (yes, no int64) {
309	cs := mustClaim(mustCourt(courtSlug), claimID)
310	return convToCC(cs.yesConvHi, cs.yesConvLo), convToCC(cs.noConvHi, cs.noConvLo)
311}
312func TrailingYes(courtSlug string, claimID uint64, window int64) (avg int64, mature bool) {
313	cs := mustClaim(mustCourt(courtSlug), claimID)
314	return cs.yes.Average(heightNow(), window)
315}
316func DocketSeries(courtSlug string, ids string, window int64) string {
317	c := mustCourt(courtSlug)
318	now := heightNow()
319	out := strings.Builder{}
320	sent := 0
321	for _, part := range strings.Split(ids, ",") {
322		if sent >= seriesPageMax {
323			break
324		}
325		part = strings.TrimSpace(part)
326		if part == "" {
327			continue
328		}
329		id, err := strconv.ParseUint(part, 10, 64)
330		if err != nil {
331			continue
332		}
333		v := c.claims.Get(beClaimKey(id))
334		if v == nil {
335			continue
336		}
337		cs := v.(*claimState)
338		oi, oiMature := cs.oi.Average(now, window)
339		yes, yesMature := cs.yes.Average(now, window)
340		if sent > 0 {
341			out.WriteString(";")
342		}
343		out.WriteString(strconv.FormatUint(id, 10))
344		out.WriteString(":" + strconv.FormatInt(cs.yesStake, 10))
345		out.WriteString(":" + strconv.FormatInt(cs.noStake, 10))
346		out.WriteString(":" + strconv.FormatInt(convToCC(cs.yesConvHi, cs.yesConvLo), 10))
347		out.WriteString(":" + strconv.FormatInt(convToCC(cs.noConvHi, cs.noConvLo), 10))
348		out.WriteString(":" + strconv.FormatInt(oi, 10))
349		out.WriteString(":" + boolDigit(oiMature))
350		out.WriteString(":" + strconv.FormatInt(yes, 10))
351		out.WriteString(":" + boolDigit(yesMature))
352		sent++
353	}
354	return out.String()
355}
356const seriesPageMax = 60
357func boolDigit(b bool) string {
358	if b {
359		return "1"
360	}
361	return "0"
362}
363func mustClaim(c *Court, id uint64) *claimState {
364	v := c.claims.Get(beClaimKey(id))
365	if v == nil {
366		panic("kourtv2: no such claim")
367	}
368	return v.(*claimState)
369}
370func beClaimKey(id uint64) string {
371	b := [8]byte{
372		byte(id >> 56), byte(id >> 48), byte(id >> 40), byte(id >> 32),
373		byte(id >> 24), byte(id >> 16), byte(id >> 8), byte(id),
374	}
375	return string(b[:])
376}