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

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}