standing.gno
6.47 Kb · 230 lines
1package kourtv3
2import (
3 "chain"
4 "strconv"
5 "strings"
6 bptree "gno.land/p/nt/bptree/v0"
7)
8const (
9 standingFlagBps = int64(10_000)
10 standingDisputeBps = int64(10_000)
11 standingAuthorHighBps = int64(2_000)
12 standingConvictionBps = int64(1_000)
13 standingUnadjudicatedBps = int64(5_000)
14)
15type creditRates struct {
16 flag, dispute, authorHigh, conviction, unadjudicated int64
17}
18var creditRatesDefault = creditRates{
19 flag: standingFlagBps,
20 dispute: standingDisputeBps,
21 authorHigh: standingAuthorHighBps,
22 conviction: standingConvictionBps,
23 unadjudicated: standingUnadjudicatedBps,
24}
25func creditRatesFor(c *Court) creditRates {
26 if c.creditRates != nil {
27 return *c.creditRates
28 }
29 return creditRatesDefault
30}
31func mustCreditRates(r creditRates) {
32 for _, v := range []int64{r.flag, r.dispute, r.authorHigh, r.conviction, r.unadjudicated} {
33 if v < 0 || v > 10_000 {
34 panic("kourtv3: a standing rate is 0..10000 bps")
35 }
36 }
37 if r.flag < r.authorHigh || r.dispute < r.authorHigh {
38 panic("kourtv3: policing must credit at least as much as authorship")
39 }
40 if r.authorHigh <= r.conviction {
41 panic("kourtv3: authorship must credit more than staking — it is the harder gate")
42 }
43 if r.unadjudicated <= 0 || r.unadjudicated >= 10_000 {
44 panic("kourtv3: the unadjudicated rate must be a real fraction of the adjudicated one")
45 }
46}
47func SetCreditRatesDefault(cur realm, flag, dispute, authorHigh, conviction, unadjudicated int64) {
48 if !cur.IsCurrent() {
49 panic(errStaleRealm)
50 }
51 d := ensureGlobalDAO()
52 if cur.Previous().Address() != d.admin {
53 panic("kourtv3: only the global DAO admin sets the default standing rates")
54 }
55 r := creditRates{flag, dispute, authorHigh, conviction, unadjudicated}
56 mustCreditRates(r)
57 creditRatesDefault = r
58 chain.Emit(globalActEvent,
59 "court", "*",
60 "claim", "0",
61 "act", "set-rates-default:"+creditRatesKey(r),
62 "by", cur.Previous().Address().String(),
63 "height", eventHeight(),
64 )
65}
66func SetCourtCreditRates(cur realm, courtSlug string, flag, dispute, authorHigh, conviction, unadjudicated int64) {
67 if !cur.IsCurrent() {
68 panic(errStaleRealm)
69 }
70 who := cur.Previous().Address()
71 c := mustCourt(courtSlug)
72 cm := ensureMod(c)
73 requireActiveMod(cm, who)
74 r := creditRates{flag, dispute, authorHigh, conviction, unadjudicated}
75 mustCreditRates(r)
76 if fire, _ := approveAction(cm.pending, "setrates:"+creditRatesKey(r), who, "",
77 speechM(cm)); !fire {
78 return
79 }
80 c.creditRates = &r
81 cm.appendCourtLog(who, "setrates:"+creditRatesKey(r), "")
82 emitModAct(c.id, 0, "setrates", who)
83}
84func creditRatesKey(r creditRates) string {
85 return strconv.FormatInt(r.flag, 10) + ":" + strconv.FormatInt(r.dispute, 10) +
86 ":" + strconv.FormatInt(r.authorHigh, 10) + ":" +
87 strconv.FormatInt(r.conviction, 10) + ":" +
88 strconv.FormatInt(r.unadjudicated, 10)
89}
90func ClearCourtCreditRates(cur realm, courtSlug string) {
91 if !cur.IsCurrent() {
92 panic(errStaleRealm)
93 }
94 who := cur.Previous().Address()
95 c := mustCourt(courtSlug)
96 cm := ensureMod(c)
97 requireMod(cm, who)
98 if c.creditRates == nil {
99 panic("kourtv3: this court has no standing-rate override")
100 }
101 if fire, _ := approveAction(cm.pending, "clearrates:"+c.id, who, "",
102 speechM(cm)); !fire {
103 return
104 }
105 c.creditRates = nil
106 cm.appendCourtLog(who, "clearrates", "")
107 emitModAct(c.id, 0, "clearrates", who)
108}
109func CreditRates(courtSlug string) string {
110 c := mustCourt(courtSlug)
111 r := creditRatesFor(c)
112 inherited := "1"
113 if c.creditRates != nil {
114 inherited = "0"
115 }
116 return "flag:" + strconv.FormatInt(r.flag, 10) +
117 ";dispute:" + strconv.FormatInt(r.dispute, 10) +
118 ";author:" + strconv.FormatInt(r.authorHigh, 10) +
119 ";conviction:" + strconv.FormatInt(r.conviction, 10) +
120 ";unadjudicated:" + strconv.FormatInt(r.unadjudicated, 10) +
121 ";inherited:" + inherited
122}
123var standingCats = [standingCatN]string{"flag", "dispute", "author", "conviction"}
124const (
125 standingCatFlag = iota
126 standingCatDispute
127 standingCatAuthor
128 standingCatConv
129 standingCatN
130)
131type standingRow struct {
132 score int64
133 highWater int64
134 passHeld bool
135 tokens int64
136 lastRefill int64
137 frozenUntil int64
138 frozenUntilTime int64
139 frozenEpoch int64
140 frozenGapUntil int64
141 frozenGapUntilTime int64
142 from [standingCatN]int64
143}
144func getStanding(c *Court, who address) *standingRow {
145 if c.standings == nil {
146 c.standings = bptree.NewBPTree32()
147 }
148 if v := c.standings.Get(string(who)); v != nil {
149 return v.(*standingRow)
150 }
151 r := &standingRow{}
152 c.standings.Set(string(who), r)
153 return r
154}
155func lookupStanding(c *Court, who address) *standingRow {
156 if c.standings == nil {
157 return nil
158 }
159 if v := c.standings.Get(string(who)); v != nil {
160 return v.(*standingRow)
161 }
162 return nil
163}
164func creditStanding(c *Court, who address, amount int64, cat int) {
165 if amount <= 0 || who == "" || cat < 0 || cat >= standingCatN {
166 return
167 }
168 r := getStanding(c, who)
169 r.score = satAdd(r.score, amount)
170 r.from[cat] = satAdd(r.from[cat], amount)
171 if r.score > r.highWater {
172 r.highWater = r.score
173 }
174}
175func satAdd(a, b int64) int64 {
176 const maxInt64 = int64(9223372036854775807)
177 if b > 0 && a > maxInt64-b {
178 return maxInt64
179 }
180 return a + b
181}
182func scaleBps(base, bps int64) int64 {
183 if base <= 0 || bps <= 0 {
184 return 0
185 }
186 if bps > 10_000 {
187 bps = 10_000
188 }
189 return mulDiv128(base, bps, 10_000)
190}
191func creditDisputeResolved(c *Court, cs *claimState) {
192 if cs.decidedRounds <= 0 {
193 return
194 }
195 if cs.provisional != cs.answer {
196 creditStanding(c, cs.overturnBy, scaleBps(cs.overturnBond, creditRatesFor(c).dispute), standingCatDispute)
197 return
198 }
199 if cs.credEligible {
200 creditStanding(c, cs.answerer, scaleBps(cs.answerBond0, creditRatesFor(c).dispute), standingCatDispute)
201 }
202}
203func creditWinConviction(c *Court, cs *claimState, who address, p *stakePos) {
204 r := creditRatesFor(c)
205 bps := r.conviction
206 if !adjudicated(cs) {
207 bps = bps * r.unadjudicated / 10_000
208 }
209 creditStanding(c, who, scaleBps(timeAvgStake(cs, p.rawHi, p.rawLo), bps), standingCatConv)
210}
211func adjudicated(cs *claimState) bool { return cs.decidedPID != 0 }
212func Standing(courtSlug string, who address) int64 {
213 if r := lookupStanding(mustCourt(courtSlug), who); r != nil {
214 return r.score
215 }
216 return 0
217}
218func StandingBreakdown(courtSlug string, who address) string {
219 var row standingRow
220 if r := lookupStanding(mustCourt(courtSlug), who); r != nil {
221 row = *r
222 }
223 var b strings.Builder
224 b.WriteString("score:" + strconv.FormatInt(row.score, 10))
225 b.WriteString(";highwater:" + strconv.FormatInt(row.highWater, 10))
226 for i, name := range standingCats {
227 b.WriteString(";" + name + ":" + strconv.FormatInt(row.from[i], 10))
228 }
229 return b.String()
230}