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.

trust.gno

5.14 Kb · 182 lines
  1// Package trust implements the scoring rules behind Guildhall reputation.
  2//
  3// Reputation is earned one record at a time. When a piece of work is paid
  4// out, the realm that recorded it asks this package how many points the work
  5// is worth. The answer depends on who vouched for it: every approving
  6// reviewer contributes their current trust, and the record is worth
  7// Base points scaled by the average trust of its reviewers.
  8//
  9// Trust is expressed in basis points (0..10000):
 10//
 11//   - seed reviewers (chosen by the realm that uses this package) always carry full trust;
 12//   - everyone else carries trust proportional to their own score, reaching
 13//     full trust at TrustThreshold and never exceeding it;
 14//   - a reviewer who is also the worker carries no trust at all.
 15//
 16// With the default TrustFloorBps of 0, work vouched for only by strangers is
 17// still recorded but earns nothing. This is what makes the score resistant to
 18// sybils: a ring of fresh addresses reviewing each other stays at zero until
 19// someone already trusted vouches for one of them. Trust flows outward from
 20// the seeds, EigenTrust-style, one paid record at a time.
 21//
 22// Scores are computed incrementally at record time, never retroactively, so
 23// every point on a profile can be traced to one record and the trust its
 24// reviewers held when they approved it.
 25package trust
 26
 27import (
 28	"errors"
 29)
 30
 31// MaxBps is the basis-point denominator used for trust and shares.
 32const MaxBps int64 = 10000
 33
 34// Params tunes the scoring rules.
 35type Params struct {
 36	// Base is the number of points a record earns when every reviewer
 37	// carries full trust and the worker received the full payout.
 38	Base int64
 39	// TrustThreshold is the score at which a non-seed reviewer reaches
 40	// full trust.
 41	TrustThreshold int64
 42	// TrustFloorBps is the minimum trust any eligible reviewer carries.
 43	TrustFloorBps int64
 44	// SelfFundedFactorBps scales the points of work funded by the worker
 45	// themselves. Zero means self-funded work earns nothing.
 46	SelfFundedFactorBps int64
 47}
 48
 49// DefaultParams returns the parameters Guildhall launches with.
 50func DefaultParams() Params {
 51	return Params{
 52		Base:                100,
 53		TrustThreshold:      500,
 54		TrustFloorBps:       0,
 55		SelfFundedFactorBps: 0,
 56	}
 57}
 58
 59// Validate reports whether the parameters are usable.
 60func (p Params) Validate() error {
 61	if p.Base <= 0 {
 62		return errors.New("trust: base must be positive")
 63	}
 64	if p.TrustThreshold <= 0 {
 65		return errors.New("trust: threshold must be positive")
 66	}
 67	if p.TrustFloorBps < 0 || p.TrustFloorBps > MaxBps {
 68		return errors.New("trust: floor must be within 0..10000 bps")
 69	}
 70	if p.SelfFundedFactorBps < 0 || p.SelfFundedFactorBps > MaxBps {
 71		return errors.New("trust: self-funded factor must be within 0..10000 bps")
 72	}
 73	return nil
 74}
 75
 76// Reviewer describes a reviewer at the moment they vouched for a record.
 77type Reviewer struct {
 78	Addr  address
 79	Score int64
 80	Seed  bool
 81}
 82
 83// Input describes a paid piece of work.
 84type Input struct {
 85	Worker    address
 86	Poster    address
 87	Reviewers []Reviewer
 88	// ShareBps is the fraction of the milestone paid to the worker, in basis
 89	// points. Approved milestones pay 10000; arbitrated disputes may pay
 90	// less.
 91	ShareBps int64
 92}
 93
 94// Result is the scoring outcome for one record.
 95type Result struct {
 96	// Points awarded to the worker.
 97	Points int64
 98	// TrustBps holds each reviewer's trust, index-aligned with
 99	// Input.Reviewers.
100	TrustBps []int64
101	// AvgTrustBps is the mean trust across reviewers.
102	AvgTrustBps int64
103}
104
105// TrustOf returns the trust, in basis points, carried by a reviewer.
106func TrustOf(r Reviewer, worker address, p Params) int64 {
107	if r.Addr == worker {
108		return 0
109	}
110	if r.Seed {
111		return MaxBps
112	}
113	t := int64(0)
114	if r.Score > 0 {
115		if r.Score >= p.TrustThreshold {
116			t = MaxBps
117		} else {
118			t = r.Score * MaxBps / p.TrustThreshold
119		}
120	}
121	if t < p.TrustFloorBps {
122		t = p.TrustFloorBps
123	}
124	return t
125}
126
127// Evaluate scores a paid piece of work.
128func Evaluate(in Input, p Params) (Result, error) {
129	if err := p.Validate(); err != nil {
130		return Result{}, err
131	}
132	if in.ShareBps < 0 || in.ShareBps > MaxBps {
133		return Result{}, errors.New("trust: share must be within 0..10000 bps")
134	}
135	res := Result{TrustBps: make([]int64, len(in.Reviewers))}
136	if len(in.Reviewers) == 0 {
137		return res, nil
138	}
139
140	seen := map[address]bool{}
141	sum := int64(0)
142	counted := int64(0)
143	for i, r := range in.Reviewers {
144		if seen[r.Addr] {
145			// A reviewer counts once, however many times they appear.
146			continue
147		}
148		seen[r.Addr] = true
149		t := TrustOf(r, in.Worker, p)
150		res.TrustBps[i] = t
151		sum += t
152		counted++
153	}
154	res.AvgTrustBps = sum / counted
155
156	points := p.Base * res.AvgTrustBps / MaxBps
157	points = points * in.ShareBps / MaxBps
158	if in.Poster == in.Worker {
159		points = points * p.SelfFundedFactorBps / MaxBps
160	}
161	res.Points = points
162	return res, nil
163}
164
165// Level names a score band. Levels are cosmetic; realms that gate on
166// reputation should compare scores directly.
167func Level(score int64) string {
168	switch {
169	case score >= 5000:
170		return "Grandmaster"
171	case score >= 2000:
172		return "Master"
173	case score >= 800:
174		return "Artisan"
175	case score >= 300:
176		return "Journeyman"
177	case score > 0:
178		return "Apprentice"
179	default:
180		return "Newcomer"
181	}
182}