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

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}