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

policy.gno

4.23 Kb · 161 lines
  1package memba_gov
  2
  3import "time"
  4
  5// Classes. A target hard-codes the minimum class of each action it governs.
  6const (
  7	Routine   = 1
  8	Financial = 2
  9	Critical  = 3
 10)
 11
 12// A route is one way a proposal passes: a rule on the YES votes of the
 13// seated roster and the delay that follows. Thresholds are fractions of the
 14// seated weight W and persons N. Every rule needs at least two persons: the
 15// critical ones because N is at least 2.
 16type route struct {
 17	rule  int
 18	delay time.Duration
 19}
 20
 21const (
 22	ruleRoutine = iota
 23	ruleFinancial
 24	ruleWeighted
 25	ruleHeadcount
 26)
 27
 28func meets(rule, w, p, W, N int) bool {
 29	switch rule {
 30	case ruleRoutine: // >= 3/8 of the weight
 31		return 8*w >= 3*W && p >= 2
 32	case ruleFinancial: // >= 3/5 of the weight and a majority of persons
 33		return 5*w >= 3*W && p >= 3 && 2*p > N
 34	case ruleWeighted: // >= 2/3 of the weight and a majority of persons
 35		return 3*w >= 2*W && 2*p > N
 36	case ruleHeadcount: // >= 2/3 of the persons and a majority of the weight
 37		return 3*p >= 2*N && 2*w > W
 38	}
 39	return false
 40}
 41
 42// routes returns the ways proposal p passes: its class sets the rules, and
 43// a roster action of this realm has its own delay on every route.
 44func routes(p *proposal) []route {
 45	var rs []route
 46	switch p.class {
 47	case Routine:
 48		rs = []route{{ruleRoutine, 0}}
 49	case Financial:
 50		rs = []route{{ruleFinancial, 0}}
 51	default:
 52		rs = []route{{ruleWeighted, WeightedDelay}, {ruleHeadcount, HeadcountDelay}}
 53	}
 54	if p.target == corePath {
 55		for i := range rs {
 56			rs[i].delay = rosterDelay(p.action)
 57		}
 58	}
 59	return rs
 60}
 61
 62// A streak is one current YES ballot: when it began and its voter's weight.
 63type streak struct {
 64	since  int64
 65	weight int
 66}
 67
 68// yesStreaks returns p's current YES ballots, oldest streak first.
 69func yesStreaks(p *proposal) []streak {
 70	var ys []streak
 71	for _, b := range p.ballots {
 72		if b.vote != voteYes {
 73			continue
 74		}
 75		x := streak{b.since, weightOf(b.person)}
 76		ys = append(ys, x)
 77		i := len(ys) - 1
 78		for ; i > 0 && ys[i-1].since > x.since; i-- {
 79			ys[i] = ys[i-1]
 80		}
 81		ys[i] = x
 82	}
 83	return ys
 84}
 85
 86// qualifiedSince returns when (unix seconds) the earliest coalition still
 87// voting YES without interruption first met rule, or 0. The coalition of a
 88// start time is every YES streak begun no later; every rule only grows with
 89// more votes, so the first prefix of ys that meets it ends on that time
 90// (streaks begun in the same second share it). A voter the rest of the
 91// coalition does not need cannot postpone the clock by withdrawing and
 92// rejoining.
 93func qualifiedSince(ys []streak, rule, W, N int) int64 {
 94	w, n := 0, 0
 95	for _, x := range ys {
 96		w += x.weight
 97		n++
 98		if meets(rule, w, n, W, N) {
 99			return x.since
100		}
101	}
102	return 0
103}
104
105// Proposal states, in the order a reader checks them.
106const (
107	stExecuted    = "executed"
108	stInvalidated = "invalidated"
109	stExpired     = "expired"
110	stReady       = "ready"
111	stTimelocked  = "timelocked"
112	stVoting      = "voting"
113)
114
115// state returns p's status and, once some route qualified, the earliest
116// unix time it is or becomes executable (0 before). Voting ends just before
117// the deadline second; readyAt and the end of the execution window are
118// inclusive.
119func state(p *proposal) (string, int64) {
120	if p.consumed {
121		return stExecuted, 0
122	}
123	if p.globalGen != globalGen || p.scopeGen != scopeGen(p.target, p.scope) || vetoed(p) {
124		return stInvalidated, 0
125	}
126	t := now()
127	W, N := seatedTotals()
128	ys := yesStreaks(p)
129	var readyAt, longest int64
130	for _, r := range routes(p) {
131		if d := secs(r.delay); d > longest {
132			longest = d
133		}
134		if since := qualifiedSince(ys, r.rule, W, N); since != 0 {
135			if at := since + secs(r.delay); readyAt == 0 || at < readyAt {
136				readyAt = at
137			}
138		}
139	}
140	deadline := p.created + secs(VotingPeriod)
141	if t > deadline+longest+secs(ExecutionWindow) || (readyAt == 0 && t >= deadline) {
142		return stExpired, readyAt
143	}
144	switch {
145	case readyAt == 0:
146		return stVoting, readyAt
147	case t < readyAt:
148		return stTimelocked, readyAt
149	}
150	return stReady, readyAt
151}
152
153// vetoed reports whether the member a RemoveInactive names has been active
154// since the proposal: proposing, voting, executing or joining vetoes it.
155func vetoed(p *proposal) bool {
156	if p.target != corePath || p.action != actRemoveInactive {
157		return false
158	}
159	m := personByID(p.subject)
160	return m != nil && m.lastActive >= p.created
161}