openrewards.gno
8.31 Kb · 299 lines
1package kourt
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("kourtv2: already rewardsOpened")
14 }
15 if cs.verdictAt == 0 {
16 panic("kourtv2: no final verdict yet")
17 }
18 if cs.closed {
19 panic("kourtv2: 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("kourtv2: 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("kourtv2: the draw has not rewardsOpened")
71 }
72 if side != int(cs.provisional) {
73 panic("kourtv2: 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("kourtv2: no position on that side")
78 }
79 p := v.(*stakePos)
80 if p.bonusPaid {
81 panic("kourtv2: 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("kourtv2: the draw has not rewardsOpened")
105 }
106 if who != cs.author {
107 panic("kourtv2: only the author draws this slice")
108 }
109 if cs.authorPaid {
110 panic("kourtv2: 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("kourtv2: the draw has not rewardsOpened")
136 }
137 if who != cs.answerer {
138 panic("kourtv2: only the answerer draws this slice")
139 }
140 if cs.answererPaid {
141 panic("kourtv2: 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("kourtv2: the draw has not rewardsOpened")
178 }
179 if isParticipant(cs, who) {
180 panic("kourtv2: participants take their own slices, never the carrot")
181 }
182 if cs.voted == nil {
183 panic("kourtv2: nobody voted on this claim")
184 }
185 if cs.decidedPID == 0 {
186 panic("kourtv2: 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("kourtv2: 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("kourtv2: 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("kourtv2: 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("kourtv2: div128 domain")
227 }
228 if hi >= uint64(den) {
229 panic("kourtv2: 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}