PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/samcrew/memba_weighted_policy

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
memba_weighted_policy
Namespace
samcrew
Files
2 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/samcrew/memba_weighted_policy
gno
0.9

Files (2)

  • gnomod.tomltoml
  • policy.gnogno
policy.gnogno
1// Package memba_weighted_policy implements the approved founding coalition and delay2// rules. The hosting DAO must authenticate callers, classify and freeze typed3// actions, and keep every Policy and Proposal private. This package grants no4// authority over a realm and is not a replacement for those integration gates.5package memba_weighted_policy67import (8	"strings"9	"time"10)1112type Category string1314const (15	Routine   Category = "routine"16	Financial Category = "financial"17	Critical  Category = "critical"18)1920// VotingPeriod is fixed by the approved founding policy, not proposer input.21const VotingPeriod = 7 * 24 * time.Hour2223type Vote string2425const (26	Yes     Vote = "yes"27	No      Vote = "no"28	Abstain Vote = "abstain"29)3031// PersonID is the directory's unique human identity, never a wallet alias.32// The host must establish that different PersonIDs really are different people.33type Member struct {34	PersonID string35	Address  address36	Founder  bool37}3839// Policy fixes one founding seven-person roster. Reconfiguration deliberately40// invalidates every pending proposal, including already mature proposals.41// Only a reviewed critical-action handler in the host may call Reconfigure.42type Policy struct {43	members    [7]Member44	generation uint6445}4647func New(members []Member) *Policy {48	return &Policy{members: validateRoster(members), generation: 1}49}5051func validateRoster(members []Member) [7]Member {52	if len(members) != 7 {53		panic("policy requires seven people")54	}55	var roster [7]Member56	founders := 057	for i, m := range members {58		if m.PersonID == "" || strings.TrimSpace(m.PersonID) != m.PersonID || !m.Address.IsValid() {59			panic("invalid member identity")60		}61		for j := 0; j < i; j++ {62			if roster[j].PersonID == m.PersonID || roster[j].Address == m.Address {63				panic("duplicate person or address")64			}65		}66		if m.Founder {67			founders++68		}69		roster[i] = m70	}71	if founders != 1 {72		panic("policy requires one founder and six developers")73	}74	return roster75}7677// Reconfigure is a host-only operation, not an authenticated public DAO API.78// It preserves the approved seven seats and 2/1 weights. Invalid input cannot79// mutate the roster or invalidate proposals, even if the host recovers a panic.80func (p *Policy) Reconfigure(members []Member) {81	roster := validateRoster(members)82	if p.generation == ^uint64(0) {83		panic("policy generation exhausted")84	}85	p.members = roster86	p.generation++87}8889func (p *Policy) memberIndex(who address) int {90	if p.generation == 0 {91		return -192	}93	for i, m := range p.members {94		if m.Address == who {95			return i96		}97	}98	return -199}100101// MayPause establishes member eligibility only. The host must dispatch only a102// typed, allowlisted pause operation; this is never spend or unpause authority.103func (p *Policy) MayPause(who address) bool { return p.memberIndex(who) >= 0 }104105// Proposal freezes the category, exact action bytes and roster generation.106// The host determines the category from the action handler, never user input.107// Each proposal has its own vote storage and qualification clocks.108// yesSince[i] is the start of seat i's current uninterrupted YES ballot and is109// meaningful only while votes[i] is Yes.110type Proposal struct {111	policy         *Policy112	generation     uint64113	category       Category114	action         string115	votes          [7]Vote116	yesSince       [7]time.Time117	createdAt      time.Time118	votingDeadline time.Time119	lastChange     time.Time120	consumed       bool121}122123func (p *Policy) NewProposal(category Category, action string) *Proposal {124	if p.generation == 0 {125		panic("uninitialized policy")126	}127	if category != Routine && category != Financial && category != Critical {128		panic("unknown action category")129	}130	if action == "" {131		panic("empty action")132	}133	now := time.Now()134	return &Proposal{policy: p, generation: p.generation, category: category, action: action, createdAt: now, votingDeadline: now.Add(VotingPeriod), lastChange: now}135}136137func (p *Proposal) assertActive() {138	if p.consumed || p.generation != p.policy.generation {139		panic("proposal consumed or invalidated")140	}141}142143// Vote requires a caller identity already authenticated by the host realm.144// Voting closes at the deadline, including changes to existing ballots.145// A critical route starts when some coalition that is still voting YES without146// interruption first met its threshold. A withdrawal moves the clock only when147// every such coalition needed that ballot, so voters the rest of the YES set148// does not need cannot postpone execution by withdrawing and rejoining.149func (p *Proposal) Vote(who address, vote Vote) {150	p.assertActive()151	i := p.policy.memberIndex(who)152	if i < 0 {153		panic("voter is not a member")154	}155	if vote != Yes && vote != No && vote != Abstain {156		panic("invalid vote")157	}158	now := time.Now()159	if now.Before(p.lastChange) {160		panic("chain time regressed")161	}162	if !now.Before(p.votingDeadline) {163		panic("voting closed")164	}165	if p.votes[i] == vote {166		return167	}168	p.votes[i] = vote169	p.lastChange = now170	if vote == Yes {171		p.yesSince[i] = now172	}173}174175// qualifiedSince returns the earliest start of an uninterrupted YES coalition176// meeting the weighted (6 points, 4 people) or developer (5 developers)177// critical threshold, or the zero time when no current coalition qualifies.178// Candidate starts are the seats' own streak starts; the coalition for a179// candidate is every current YES ballot that began no later than it.180func (p *Proposal) qualifiedSince(developersOnly bool) time.Time {181	var earliest time.Time182	for c, candidate := range p.votes {183		if candidate != Yes {184			continue185		}186		start := p.yesSince[c]187		if !earliest.IsZero() && !start.Before(earliest) {188			continue189		}190		weight, people := 0, 0191		for j, vote := range p.votes {192			if vote != Yes || p.yesSince[j].After(start) {193				continue194			}195			founder := p.policy.members[j].Founder196			if developersOnly && founder {197				continue198			}199			people++200			weight++201			if founder {202				weight++203			}204		}205		if developersOnly && people >= 5 || !developersOnly && weight >= 6 && people >= 4 {206			earliest = start207		}208	}209	return earliest210}211212func (p *Proposal) tally() (weight, people, developers int) {213	for i, vote := range p.votes {214		if vote != Yes {215			continue216		}217		people++218		weight++219		if p.policy.members[i].Founder {220			weight++221		} else {222			developers++223		}224	}225	return226}227228// State is a copy for rendering. Reading never starts or advances a clock.229// An invalidated proposal reports no current tally or execution authorization.230type State struct {231	Category       Category232	CreatedAt      time.Time233	VotingDeadline time.Time234	VotingClosed   bool235	Qualified      bool236	Expired        bool237	WeightYes      int238	PeopleYes      int239	DevelopersYes  int240	WeightedAfter  time.Time241	DeveloperAfter time.Time242	Invalidated    bool243	Consumed       bool244	Ready          bool245}246247func (p *Proposal) State() State {248	now := time.Now()249	s := State{Category: p.category, CreatedAt: p.createdAt, VotingDeadline: p.votingDeadline, VotingClosed: !now.Before(p.votingDeadline), Invalidated: p.generation != p.policy.generation, Consumed: p.consumed}250	if s.Invalidated || s.Consumed {251		return s252	}253	s.WeightYes, s.PeopleYes, s.DevelopersYes = p.tally()254	if now.Before(p.lastChange) {255		return s256	}257	switch p.category {258	case Routine:259		s.Qualified = s.WeightYes >= 3 && s.PeopleYes >= 2260		s.Ready = s.Qualified261	case Financial:262		s.Qualified = s.WeightYes >= 5 && s.PeopleYes >= 3263		s.Ready = s.Qualified264	case Critical:265		weightedSince := p.qualifiedSince(false)266		developerSince := p.qualifiedSince(true)267		s.Qualified = !weightedSince.IsZero() || !developerSince.IsZero()268		if !weightedSince.IsZero() {269			s.WeightedAfter = weightedSince.Add(24 * time.Hour)270			s.Ready = !now.Before(s.WeightedAfter)271		}272		if !developerSince.IsZero() {273			s.DeveloperAfter = developerSince.Add(72 * time.Hour)274			s.Ready = s.Ready || !now.Before(s.DeveloperAfter)275		}276	}277	// Ballots are immutable after closing. A pre-deadline qualification can278	// finish its existing delay afterward; an unqualified ballot cannot revive.279	s.Expired = s.VotingClosed && !s.Qualified280	return s281}282283// Consume binds execution to the frozen action and marks it consumed BEFORE284// the host invokes its typed handler. The host must let handler failure abort285// the transaction: recovering a panic does not roll back Gno realm state.286// Caller authentication and current-member executor eligibility belong to the287// host. Do not expose Proposal pointers or call Consume from a read endpoint.288func (p *Proposal) Consume(action string) {289	p.assertActive()290	if action != p.action {291		panic("action changed")292	}293	if !p.State().Ready {294		panic("proposal is not ready")295	}296	p.consumed = true297}298

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.