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

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}