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.

openrewards.gno

8.31 Kb · 299 lines
  1package kourtv3
  2import (
  3	"math/bits"
  4)
  5func OpenRewards(cur realm, courtSlug string, claimID uint64) {
  6	if !cur.IsCurrent() {
  7		panic(errStaleRealm)
  8	}
  9	who := cur.Previous().Address()
 10	c := mustCourt(courtSlug)
 11	cs := mustClaim(c, claimID)
 12	if cs.rewardsOpened {
 13		panic("kourtv3: already rewardsOpened")
 14	}
 15	if cs.verdictAt == 0 {
 16		panic("kourtv3: no final verdict yet")
 17	}
 18	if cs.closed {
 19		panic("kourtv3: dead claims have no draw")
 20	}
 21	now := heightNow()
 22	if passed, known := pastDeadline(cs.verdictAtTime, finalizeGraceSecs); ((known && !passed) ||
 23		(!known && now < cs.verdictAt+finalizeGraceBlocks)) && !isParticipant(cs, who) {
 24		panic("kourtv3: OpenRewards is participant-only for its first week")
 25	}
 26	touch(c)
 27	advancePools(c, cs)
 28	cs.rewardsOpened = true
 29	stripExit(c, cs)
 30	releaseMetaLatchIfMeta(c, cs)
 31	cs.openBlocks = cs.frozenAt - (cs.openedAt + c.params.stakeOpenDelayBlocks)
 32	if cs.openBlocks < 1 {
 33		cs.openBlocks = 1
 34	}
 35	var midGross int64
 36	if int(cs.provisional) == sideYES {
 37		midGross = convToCC(cs.yesConvHi, cs.yesConvLo)
 38	} else {
 39		midGross = convToCC(cs.noConvHi, cs.noConvLo)
 40	}
 41	cs.winPoolConvCC = midGross
 42	want := drawWant(c, cs, midGross)
 43	d := reserveJunior(c, want)
 44	cs.drawWinners, cs.drawAuthor, cs.drawAnswerer = drawSplit(cs, d)
 45	cs.carrotTotal = mulDiv128(midGross, splitCarrot, 100)
 46	cs.carrotPool = cs.carrotTotal
 47	if cs.deposit > 0 {
 48		c.coin.Transfer(c.escrow, cs.author, cs.deposit)
 49		c.deposit -= cs.deposit
 50		cs.deposit = 0
 51	}
 52	if cs.fee > 0 {
 53		c.coin.Transfer(c.escrow, cs.author, cs.fee)
 54		c.deposit -= cs.fee
 55		cs.fee = 0
 56	}
 57	if cs.answerBond > 0 {
 58		settleAnswerBond(c, cs)
 59	}
 60}
 61func WithdrawBonus(cur realm, courtSlug string, claimID uint64, side int) int64 {
 62	if !cur.IsCurrent() {
 63		panic(errStaleRealm)
 64	}
 65	who := cur.Previous().Address()
 66	c := mustCourt(courtSlug)
 67	cs := mustClaim(c, claimID)
 68	mustSide(side)
 69	if !cs.rewardsOpened {
 70		panic("kourtv3: the draw has not rewardsOpened")
 71	}
 72	if side != int(cs.provisional) {
 73		panic("kourtv3: only the winning side draws (principal came back either way)")
 74	}
 75	v := cs.stakers.Get(posKey(who, side))
 76	if v == nil {
 77		panic("kourtv3: no position on that side")
 78	}
 79	p := v.(*stakePos)
 80	if p.bonusPaid {
 81		panic("kourtv3: this position's bonus was already drawn")
 82	}
 83	touch(c)
 84	accrue(c, cs, p, side)
 85	p.bonusPaid = true
 86	convCC := convToCC(p.convHi, p.convLo)
 87	creditWinConviction(c, cs, who, p)
 88	if convCC <= 0 || cs.winPoolConvCC <= 0 {
 89		return 0
 90	}
 91	gross := mulDiv128(cs.drawWinners, convCC, cs.winPoolConvCC)
 92	pay := capBonus(cs, gross, p.rawHi, p.rawLo)
 93	mintEmission(c, who, pay)
 94	return pay
 95}
 96func AuthorBonus(cur realm, courtSlug string, claimID uint64) int64 {
 97	if !cur.IsCurrent() {
 98		panic(errStaleRealm)
 99	}
100	who := cur.Previous().Address()
101	c := mustCourt(courtSlug)
102	cs := mustClaim(c, claimID)
103	if !cs.rewardsOpened {
104		panic("kourtv3: the draw has not rewardsOpened")
105	}
106	if who != cs.author {
107		panic("kourtv3: only the author draws this slice")
108	}
109	if cs.authorPaid {
110		panic("kourtv3: the author slice was already drawn")
111	}
112	touch(c)
113	cs.authorPaid = true
114	var pay int64
115	if cs.seeded {
116		mintEmission(c, who, 0)
117		return 0
118	}
119	if v := cs.stakers.Get(posKey(who, int(cs.provisional))); v != nil {
120		p := v.(*stakePos)
121		accrue(c, cs, p, int(cs.provisional))
122		pay = capBonus(cs, cs.drawAuthor, p.rawHi, p.rawLo)
123	}
124	mintEmission(c, who, pay)
125	return pay
126}
127func AnswererBonus(cur realm, courtSlug string, claimID uint64) int64 {
128	if !cur.IsCurrent() {
129		panic(errStaleRealm)
130	}
131	who := cur.Previous().Address()
132	c := mustCourt(courtSlug)
133	cs := mustClaim(c, claimID)
134	if !cs.rewardsOpened {
135		panic("kourtv3: the draw has not rewardsOpened")
136	}
137	if who != cs.answerer {
138		panic("kourtv3: only the answerer draws this slice")
139	}
140	if cs.answererPaid {
141		panic("kourtv3: the answerer slice was already drawn")
142	}
143	touch(c)
144	cs.answererPaid = true
145	pay := cs.drawAnswerer
146	if bound := mulDiv128(cs.answerBond0, tierBpsFor(cs), 2*tierParBps); pay > bound {
147		pay = bound
148	}
149	mintEmission(c, who, pay)
150	return pay
151}
152func timeAvgStake(cs *claimState, rawHi, rawLo uint64) int64 {
153	if cs.openBlocks <= 0 || rawHi >= uint64(cs.openBlocks) {
154		return 0
155	}
156	return div128(rawHi, rawLo, cs.openBlocks)
157}
158func capBonus(cs *claimState, gross int64, rawHi, rawLo uint64) int64 {
159	rawAvg := timeAvgStake(cs, rawHi, rawLo)
160	bound := mulDiv128(rawAvg, tierBpsFor(cs), 2*tierParBps)
161	if gross > bound {
162		gross = bound
163	}
164	if gross < 0 {
165		gross = 0
166	}
167	return gross
168}
169func PullCarrot(cur realm, courtSlug string, claimID uint64) int64 {
170	if !cur.IsCurrent() {
171		panic(errStaleRealm)
172	}
173	who := cur.Previous().Address()
174	c := mustCourt(courtSlug)
175	cs := mustClaim(c, claimID)
176	if !cs.rewardsOpened {
177		panic("kourtv3: the draw has not rewardsOpened")
178	}
179	if isParticipant(cs, who) {
180		panic("kourtv3: participants take their own slices, never the carrot")
181	}
182	if cs.voted == nil {
183		panic("kourtv3: nobody voted on this claim")
184	}
185	if cs.decidedPID == 0 {
186		panic("kourtv3: no vote resolved this claim; there is no carrot")
187	}
188	v := cs.voted.Get("d" + beClaimKey(uint64(cs.decidedPID)) + string(who))
189	if v == nil || v.(string) != cs.carrotChoice {
190		panic("kourtv3: the carrot pays with-verdict voters of the deciding round")
191	}
192	vw := cs.voted.Get("dw" + beClaimKey(uint64(cs.decidedPID)) + string(who))
193	if vw == nil {
194		panic("kourtv3: no recorded weight for that voter on the deciding round")
195	}
196	w, denom := vw.(int64), cs.carrotDenom
197	claimedKey := "c" + beClaimKey(uint64(cs.decidedPID)) + string(who)
198	if cs.voted.Has(claimedKey) {
199		panic("kourtv3: carrot already claimed")
200	}
201	cs.voted.Set(claimedKey, true)
202	if w <= 0 || denom <= 0 {
203		return 0
204	}
205	touch(c)
206	share := mulDiv128(cs.carrotTotal, w, denom)
207	b0 := cs.xBarFrozen * carrotClampXBps / 10000
208	if b0 < carrotClampMinCC {
209		b0 = carrotClampMinCC
210	}
211	if clamp := b0/2 - 1; share > clamp {
212		share = clamp
213	}
214	if share > cs.carrotPool {
215		share = cs.carrotPool
216	}
217	if share <= 0 {
218		return 0
219	}
220	cs.carrotPool -= share
221	enqueueSenior(c, who, share, "carrot")
222	return share
223}
224func div128(hi, lo uint64, den int64) int64 {
225	if den <= 0 {
226		panic("kourtv3: div128 domain")
227	}
228	if hi >= uint64(den) {
229		panic("kourtv3: div128 quotient overflow")
230	}
231	q, _ := bits.Div64(hi, lo, uint64(den))
232	return int64(q)
233}
234func RewardsOpened(courtSlug string, claimID uint64) bool {
235	return mustClaim(mustCourt(courtSlug), claimID).rewardsOpened
236}
237func drawEntitlement(c *Court, cs *claimState, midGross int64) int64 {
238	tierBps := tierBpsFor(cs)
239	want := mulDiv128(midGross, tierBps, tierParBps)
240	want = mulDiv128(want, spamNetBps(cs), tierParBps)
241	if int(cs.provisional) == int(cs.answer) {
242		if capd := cs.answerBond0 - mulDiv128(midGross, splitCarrot, 100); want > capd {
243			want = capd
244			if want < 0 {
245				want = 0
246			}
247		}
248	}
249	return want
250}
251func drawWant(c *Court, cs *claimState, midGross int64) int64 {
252	want := drawEntitlement(c, cs, midGross)
253	if want > c.curPeriodBudget {
254		want = c.curPeriodBudget
255	}
256	return want
257}
258func drawSplit(cs *claimState, d int64) (winners, author, answerer int64) {
259	winners = d * splitWinners / (splitWinners + splitAuthor + splitAnswerer)
260	author = d * splitAuthor / (splitWinners + splitAuthor + splitAnswerer)
261	answerer = d * splitAnswerer / (splitWinners + splitAuthor + splitAnswerer)
262	if int(cs.provisional) != int(cs.answer) {
263		answerer = 0
264	}
265	if cs.seeded {
266		author = 0
267	}
268	if cs.noAccuracy {
269		winners = 0
270	}
271	return winners, author, answerer
272}
273func DrawQuote(courtSlug string, claimID uint64) (winners, author, answerer int64) {
274	c := mustCourt(courtSlug)
275	cs := mustClaim(c, claimID)
276	if cs.verdictAt == 0 || cs.rewardsOpened || cs.provisional < 0 {
277		return 0, 0, 0
278	}
279	var midGross int64
280	if cs.provisional == sideYES {
281		midGross = convToCC(cs.yesConvHi, cs.yesConvLo)
282	} else {
283		midGross = convToCC(cs.noConvHi, cs.noConvLo)
284	}
285	return drawSplit(cs, drawEntitlement(c, cs, midGross))
286}
287func DrawSlices(courtSlug string, claimID uint64) (winners, author, answerer, carrotLeft int64) {
288	cs := mustClaim(mustCourt(courtSlug), claimID)
289	return cs.drawWinners, cs.drawAuthor, cs.drawAnswerer, cs.carrotPool
290}
291func PullState(courtSlug string, claimID uint64, who address) (winnerPaid, authorPaid, answererPaid bool) {
292	cs := mustClaim(mustCourt(courtSlug), claimID)
293	if cs.provisional >= 0 {
294		if v := cs.stakers.Get(posKey(who, int(cs.provisional))); v != nil {
295			winnerPaid = v.(*stakePos).bonusPaid
296		}
297	}
298	return winnerPaid, cs.authorPaid && who == cs.author, cs.answererPaid && who == cs.answerer
299}