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

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}