stake.gno
7.61 Kb · 284 lines
1package kourt
2import (
3 "math/bits"
4 "math/overflow"
5)
6const r0WeeklyBps = int64(25)
7const (
8 stepDownNum = int64(9_933_575)
9 stepDownDen = int64(10_000_000)
10)
11const budgetBpsFPStart = budgetWeeklyBps * 1_000_000
12type stakePos struct {
13 stake int64
14 convHi uint64
15 convLo uint64
16 lastRateAcc int64
17 rawHi, rawLo uint64
18 lastRawAt int64
19 bonusPaid bool
20}
21func rateBpsFP(c *Court) int64 {
22 return 255 * (r0WeeklyBps*1_000_000 + c.dEffBpsFP) / 100
23}
24func effectiveRateAcc(c *Court, cs *claimState) int64 {
25 acc := c.rateAccFP
26 if cs.frozenAt != 0 && cs.rateAccAtFreeze < acc {
27 acc = cs.rateAccAtFreeze
28 }
29 return acc
30}
31func accrue(c *Court, cs *claimState, p *stakePos, side int) {
32 acc := effectiveRateAcc(c, cs)
33 delta := acc - p.lastRateAcc
34 p.lastRateAcc = acc
35 if delta > 0 && p.stake > 0 {
36 hi, lo := bits.Mul64(uint64(p.stake), uint64(delta))
37 p.convHi, p.convLo = add128(p.convHi, p.convLo, hi, lo)
38 }
39 rh := rawHeight(cs)
40 rawDelta := rh - p.lastRawAt
41 p.lastRawAt = rh
42 if rawDelta > 0 && p.stake > 0 {
43 hi, lo := bits.Mul64(uint64(p.stake), uint64(rawDelta))
44 p.rawHi, p.rawLo = add128(p.rawHi, p.rawLo, hi, lo)
45 }
46 _ = side
47}
48func advancePools(c *Court, cs *claimState) {
49 acc := effectiveRateAcc(c, cs)
50 rh := rawHeight(cs)
51 if d := acc - cs.yesLastAcc; d > 0 && cs.yesStake > 0 {
52 hi, lo := bits.Mul64(uint64(cs.yesStake), uint64(d))
53 cs.yesConvHi, cs.yesConvLo = add128(cs.yesConvHi, cs.yesConvLo, hi, lo)
54 }
55 if d := acc - cs.noLastAcc; d > 0 && cs.noStake > 0 {
56 hi, lo := bits.Mul64(uint64(cs.noStake), uint64(d))
57 cs.noConvHi, cs.noConvLo = add128(cs.noConvHi, cs.noConvLo, hi, lo)
58 }
59 if d := rh - cs.yesLastRawAt; d > 0 && cs.yesStake > 0 {
60 hi, lo := bits.Mul64(uint64(cs.yesStake), uint64(d))
61 cs.yesRawHi, cs.yesRawLo = add128(cs.yesRawHi, cs.yesRawLo, hi, lo)
62 }
63 if d := rh - cs.noLastRawAt; d > 0 && cs.noStake > 0 {
64 hi, lo := bits.Mul64(uint64(cs.noStake), uint64(d))
65 cs.noRawHi, cs.noRawLo = add128(cs.noRawHi, cs.noRawLo, hi, lo)
66 }
67 cs.yesLastAcc, cs.noLastAcc = acc, acc
68 cs.yesLastRawAt, cs.noLastRawAt = rh, rh
69}
70func rawHeight(cs *claimState) int64 {
71 h := heightNow()
72 if cs.frozenAt != 0 && cs.frozenAt < h {
73 h = cs.frozenAt
74 }
75 return h
76}
77func lifeAvgStake(cs *claimState, now int64) int64 {
78 hi, lo := add128(cs.yesRawHi, cs.yesRawLo, cs.noRawHi, cs.noRawLo)
79 den := now - cs.openedAt
80 if den < 1 {
81 den = 1
82 }
83 return div128(hi, lo, den)
84}
85func Stake(cur realm, courtSlug string, claimID uint64, side int, amount int64) {
86 if !cur.IsCurrent() {
87 panic(errStaleRealm)
88 }
89 who := cur.Previous().Address()
90 c := mustCourt(courtSlug)
91 cs := mustClaim(c, claimID)
92 mustSide(side)
93 if amount <= 0 {
94 panic("kourtv2: stake must be positive")
95 }
96 if cs.closed {
97 panic("kourtv2: this claim is closed")
98 }
99 if cs.frozenAt != 0 {
100 panic("kourtv2: staking is frozen — this claim has an answer")
101 }
102 if passed, known := pastDeadline(cs.openedAtTime, blocksToSecs(c.params.stakeOpenDelayBlocks)); (known && !passed) ||
103 (!known && heightNow() < cs.openedAt+c.params.stakeOpenDelayBlocks) {
104 panic("kourtv2: the polish window is open; staking has not started yet")
105 }
106 touch(c)
107 mustStakable(c, who, amount)
108 lockStake(c, who, amount)
109 advancePools(c, cs)
110 p := getPos(c, cs, who, side)
111 accrue(c, cs, p, side)
112 p.stake = mustAdd(p.stake, amount)
113 if side == sideYES {
114 cs.yesStake = mustAdd(cs.yesStake, amount)
115 } else {
116 cs.noStake = mustAdd(cs.noStake, amount)
117 }
118 observePools(cs)
119 recordPools(c, cs)
120}
121func Unstake(cur realm, courtSlug string, claimID uint64, side int, amount int64) {
122 if !cur.IsCurrent() {
123 panic(errStaleRealm)
124 }
125 who := cur.Previous().Address()
126 c := mustCourt(courtSlug)
127 cs := mustClaim(c, claimID)
128 mustSide(side)
129 if amount <= 0 {
130 panic("kourtv2: unstake must be positive")
131 }
132 if cs.frozenAt != 0 {
133 panic("kourtv2: stakes are frozen — withdraw at settlement (principal is never withheld)")
134 }
135 touch(c)
136 advancePools(c, cs)
137 p := getPos(c, cs, who, side)
138 if p.stake < amount {
139 panic("kourtv2: unstaking more than is staked")
140 }
141 accrue(c, cs, p, side)
142 if amount > 0 && p.stake > 0 {
143 keep := p.stake - amount
144 convPer := div128(p.convHi, p.convLo, p.stake)
145 nCHi, nCLo := bits.Mul64(uint64(convPer), uint64(keep))
146 dCHi, dCLo := sub128(p.convHi, p.convLo, nCHi, nCLo)
147 p.convHi, p.convLo = nCHi, nCLo
148 if side == sideYES {
149 cs.yesConvHi, cs.yesConvLo = sub128(cs.yesConvHi, cs.yesConvLo, dCHi, dCLo)
150 } else {
151 cs.noConvHi, cs.noConvLo = sub128(cs.noConvHi, cs.noConvLo, dCHi, dCLo)
152 }
153 }
154 p.stake -= amount
155 releaseStake(c, who, amount)
156 if side == sideYES {
157 cs.yesStake -= amount
158 } else {
159 cs.noStake -= amount
160 }
161 observePools(cs)
162 recordPools(c, cs)
163}
164func observePools(cs *claimState) {
165 h := heightNow()
166 cs.oi = cs.oi.Observe(h, mustAdd(cs.yesStake, cs.noStake))
167 cs.yes = cs.yes.Observe(h, cs.yesStake)
168}
169func StakeOf(courtSlug string, claimID uint64, side int, who address) int64 {
170 mustSide(side)
171 cs := mustClaim(mustCourt(courtSlug), claimID)
172 if v := cs.stakers.Get(posKey(who, side)); v != nil {
173 return v.(*stakePos).stake
174 }
175 return 0
176}
177func ConvictionOf(courtSlug string, claimID uint64, side int, who address) int64 {
178 mustSide(side)
179 c := mustCourt(courtSlug)
180 cs := mustClaim(c, claimID)
181 v := cs.stakers.Get(posKey(who, side))
182 if v == nil {
183 return 0
184 }
185 return convPreviewCC(cs, v.(*stakePos), ratePreview(c))
186}
187func ratePreview(c *Court) int64 {
188 acc := c.rateAccFP
189 if tail := heightNow() - c.lastAccrual; tail > 0 {
190 acc = mustAdd(acc, mustMul(rateBpsFP(c), tail))
191 }
192 return acc
193}
194func convPreviewCC(cs *claimState, p *stakePos, acc int64) int64 {
195 if cs.frozenAt != 0 && cs.rateAccAtFreeze < acc {
196 acc = cs.rateAccAtFreeze
197 }
198 hi, lo := p.convHi, p.convLo
199 if delta := acc - p.lastRateAcc; delta > 0 && p.stake > 0 {
200 mh, ml := bits.Mul64(uint64(p.stake), uint64(delta))
201 hi, lo = add128(hi, lo, mh, ml)
202 }
203 return convToCC(hi, lo)
204}
205func convToCC(hi, lo uint64) int64 {
206 den := uint64(periodBlocks) * 10_000 * 1_000_000
207 if hi >= den {
208 panic("kourtv2: conviction exceeds payable range")
209 }
210 q, _ := bits.Div64(hi, lo, den)
211 return int64(q)
212}
213func getPos(c *Court, cs *claimState, who address, side int) *stakePos {
214 k := posKey(who, side)
215 if v := cs.stakers.Get(k); v != nil {
216 return v.(*stakePos)
217 }
218 p := &stakePos{lastRateAcc: c.rateAccFP, lastRawAt: rawHeight(cs)}
219 cs.stakers.Set(k, p)
220 indexPosition(c, cs.id, who, side)
221 return p
222}
223func posKey(who address, side int) string {
224 if side == sideYES {
225 return string(who) + "|y"
226 }
227 return string(who) + "|n"
228}
229func mustSide(side int) {
230 if side != sideYES && side != sideNO {
231 panic("kourtv2: side is 0 (YES) or 1 (NO)")
232 }
233}
234func mustMul(a, b int64) int64 {
235 m, ok := overflow.Mul64(a, b)
236 if !ok {
237 panic("kourtv2: multiply overflow")
238 }
239 return m
240}
241func mulDiv128(a, b, den int64) int64 {
242 if a < 0 || b < 0 || den <= 0 {
243 panic("kourtv2: mulDiv128 domain")
244 }
245 hi, lo := bits.Mul64(uint64(a), uint64(b))
246 if hi >= uint64(den) {
247 panic("kourtv2: mulDiv128 quotient overflow")
248 }
249 q, _ := bits.Div64(hi, lo, uint64(den))
250 if q > 9223372036854775807 {
251 panic("kourtv2: mulDiv128 quotient exceeds int64")
252 }
253 return int64(q)
254}
255func mustAdd(a, b int64) int64 {
256 s, ok := overflow.Add64(a, b)
257 if !ok {
258 panic("kourtv2: amount overflow")
259 }
260 return s
261}
262func sub128(aHi, aLo, bHi, bLo uint64) (hi, lo uint64) {
263 if aHi < bHi || (aHi == bHi && aLo < bLo) {
264 panic("kourtv2: 128-bit conviction underflow")
265 }
266 lo = aLo - bLo
267 hi = aHi - bHi
268 if aLo < bLo {
269 hi--
270 }
271 return hi, lo
272}
273func add128(aHi, aLo, bHi, bLo uint64) (hi, lo uint64) {
274 lo = aLo + bLo
275 carry := uint64(0)
276 if lo < aLo {
277 carry = 1
278 }
279 hi = aHi + bHi + carry
280 if hi < aHi || (hi == aHi && carry == 1 && bHi == ^uint64(0)) {
281 panic("kourtv2: 128-bit conviction overflow")
282 }
283 return hi, lo
284}