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.

stake.gno

7.62 Kb · 284 lines
  1package kourtv3
  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("kourtv3: stake must be positive")
 95	}
 96	if cs.closed {
 97		panic("kourtv3: this claim is closed")
 98	}
 99	if cs.frozenAt != 0 {
100		panic("kourtv3: 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("kourtv3: 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("kourtv3: unstake must be positive")
131	}
132	if cs.frozenAt != 0 {
133		panic("kourtv3: 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("kourtv3: 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("kourtv3: 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("kourtv3: 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("kourtv3: multiply overflow")
238	}
239	return m
240}
241func mulDiv128(a, b, den int64) int64 {
242	if a < 0 || b < 0 || den <= 0 {
243		panic("kourtv3: mulDiv128 domain")
244	}
245	hi, lo := bits.Mul64(uint64(a), uint64(b))
246	if hi >= uint64(den) {
247		panic("kourtv3: mulDiv128 quotient overflow")
248	}
249	q, _ := bits.Div64(hi, lo, uint64(den))
250	if q > 9223372036854775807 {
251		panic("kourtv3: 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("kourtv3: 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("kourtv3: 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("kourtv3: 128-bit conviction overflow")
282	}
283	return hi, lo
284}