emission.gno
4.49 Kb · 185 lines
1package kourtv3
2import (
3 "chain"
4 "strconv"
5)
6const (
7 seniorEnqueuedEvent = "SeniorEnqueued"
8 seniorPaidEvent = "SeniorPaid"
9)
10type entitlement struct {
11 to address
12 amount int64
13 start int64
14 paid int64
15 purpose string
16}
17func touch(c *Court) {
18 now := heightNow()
19 for c.lastAccrual < now {
20 boundary := c.createdAt + (c.periodIndex+1)*periodBlocks
21 segEnd := now
22 if boundary < segEnd {
23 segEnd = boundary
24 }
25 accrueSegment(c, segEnd-c.lastAccrual)
26 advanceRateAcc(c, segEnd-c.lastAccrual)
27 c.lastAccrual = segEnd
28 if segEnd == boundary {
29 rollPeriod(c)
30 }
31 }
32}
33func accrueSegment(c *Court, blocks int64) {
34 if blocks <= 0 || c.curPeriodBudget <= 0 {
35 return
36 }
37 if c.reservoirR() >= c.rMax() && c.cumAccrual >= c.reservedTail {
38 return
39 }
40 num := mustAdd(mustMul(c.curPeriodBudget, blocks), c.accrualRem)
41 c.cumAccrual = mustAdd(c.cumAccrual, num/periodBlocks)
42 c.accrualRem = num % periodBlocks
43}
44func advanceRateAcc(c *Court, blocks int64) {
45 if blocks <= 0 {
46 return
47 }
48 c.rateAccFP = mustAdd(c.rateAccFP, mustMul(rateBpsFP(c), blocks))
49}
50func rollPeriod(c *Court) {
51 c.periodIndex++
52 c.curBudgetBpsFP = c.curBudgetBpsFP * stepDownNum / stepDownDen
53 minted := c.emittedTotal - c.emittedAtRoll
54 c.emittedAtRoll = c.emittedTotal
55 c.emaMinted = (3*c.emaMinted + minted) / 4
56 s := c.coin.TotalSupply()
57 if s > 0 {
58 if c.emaMinted < 0 {
59 panic("kourtv3: negative realized mint")
60 }
61 dReal := mulDiv128(c.emaMinted, 10_000*1_000_000, s)
62 if dReal < c.curBudgetBpsFP {
63 c.dEffBpsFP = dReal
64 } else {
65 c.dEffBpsFP = c.curBudgetBpsFP
66 }
67 if c.absBudget > 0 {
68 c.absBudget = c.absBudget * stepDownNum / stepDownDen
69 c.curPeriodBudget = c.absBudget
70 } else {
71 c.curPeriodBudget = mulDiv128(s, c.curBudgetBpsFP, 10_000*1_000_000)
72 }
73 } else {
74 c.dEffBpsFP = 0
75 c.curPeriodBudget = 0
76 }
77}
78func (c *Court) reservoirR() int64 {
79 past := c.cumAccrual - c.reservedTail
80 if past < 0 {
81 past = 0
82 }
83 r := past - c.juniorReserved
84 if r < 0 {
85 r = 0
86 }
87 return r
88}
89func (c *Court) rMax() int64 { return rMaxPeriods * c.curPeriodBudget }
90func enqueueSenior(c *Court, to address, amount int64, purpose string) uint64 {
91 if amount <= 0 {
92 panic("kourtv3: a senior entitlement must be positive")
93 }
94 start := mustAdd(c.reservedTail, c.juniorReserved)
95 e := &entitlement{to: to, amount: amount, start: start, purpose: purpose}
96 c.reservedTail = mustAdd(c.reservedTail, amount)
97 c.seniorOwed = mustAdd(c.seniorOwed, amount)
98 c.queueSeq++
99 c.queue.Set(beClaimKey(c.queueSeq), e)
100 chain.Emit(seniorEnqueuedEvent,
101 "court", c.id,
102 "seq", strconv.FormatUint(c.queueSeq, 10),
103 "to", to.String(),
104 "amount", strconv.FormatInt(amount, 10),
105 "purpose", purpose,
106 )
107 return c.queueSeq
108}
109func reserveJunior(c *Court, want int64) int64 {
110 touch(c)
111 r := c.reservoirR()
112 if want > r {
113 want = r
114 }
115 if want < 0 {
116 want = 0
117 }
118 c.juniorReserved = mustAdd(c.juniorReserved, want)
119 return want
120}
121func mintEmission(c *Court, to address, amount int64) {
122 if amount <= 0 {
123 return
124 }
125 c.coin.Mint(to, amount)
126 c.emittedTotal = mustAdd(c.emittedTotal, amount)
127}
128func PullSenior(cur realm, courtSlug string, seq uint64) int64 {
129 if !cur.IsCurrent() {
130 panic(errStaleRealm)
131 }
132 c := mustCourt(courtSlug)
133 touch(c)
134 v := c.queue.Get(beClaimKey(seq))
135 if v == nil {
136 panic("kourtv3: no such entitlement")
137 }
138 e := v.(*entitlement)
139 covered := c.cumAccrual - e.start
140 if covered > e.amount {
141 covered = e.amount
142 }
143 pay := covered - e.paid
144 if pay <= 0 {
145 return 0
146 }
147 e.paid += pay
148 c.seniorOwed -= pay
149 c.seniorPaid = mustAdd(c.seniorPaid, pay)
150 mintEmission(c, e.to, pay)
151 chain.Emit(seniorPaidEvent,
152 "court", c.id,
153 "seq", strconv.FormatUint(seq, 10),
154 "to", e.to.String(),
155 "paid", strconv.FormatInt(pay, 10),
156 "remaining", strconv.FormatInt(e.amount-e.paid, 10),
157 )
158 return pay
159}
160func Reservoir(courtSlug string) int64 { return mustCourt(courtSlug).reservoirR() }
161func SeniorOwed(courtSlug string) int64 { return mustCourt(courtSlug).seniorOwed }
162func SeniorEntitlement(courtSlug string, seq uint64) (to address, amount, paid, payable int64) {
163 c := mustCourt(courtSlug)
164 v := c.queue.Get(beClaimKey(seq))
165 if v == nil {
166 panic("kourtv3: no such entitlement")
167 }
168 e := v.(*entitlement)
169 covered := c.cumAccrual - e.start
170 if covered > e.amount {
171 covered = e.amount
172 }
173 p := covered - e.paid
174 if p < 0 {
175 p = 0
176 }
177 return e.to, e.amount, e.paid, p
178}
179func QueueLen(courtSlug string) uint64 { return mustCourt(courtSlug).queueSeq }
180func Poke(cur realm, courtSlug string) {
181 if !cur.IsCurrent() {
182 panic(errStaleRealm)
183 }
184 touch(mustCourt(courtSlug))
185}