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_host

Package
Open in gnoweb ↗

Overview

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

Files (32)

  • gnomod.tomltoml
  • appstore_actions.gnogno
  • appstore_host.gnogno
  • appstore_reads.gnogno
  • arcade_actions.gnogno
  • arcade_host.gnogno
  • arcade_reads.gno
gno
  • badges_actions.gnogno
  • badges_host.gnogno
  • badges_reads.gnogno
  • channels_actions.gnogno
  • channels_host.gnogno
  • channels_reads.gnogno
  • escrow_actions.gnogno
  • escrow_host.gnogno
  • escrow_reads.gnogno
  • feed_actions.gnogno
  • feed_host.gnogno
  • feed_reads.gnogno
  • feedback_actions.gnogno
  • feedback_host.gnogno
  • feedback_reads.gnogno
  • host.gnogno
  • market_actions.gnogno
  • market_host.gnogno
  • quest_actions.gnogno
  • quest_host.gnogno
  • quest_reads.gnogno
  • reads.gnogno
  • reviews_actions.gnogno
  • reviews_host.gnogno
  • reviews_reads.gnogno
  • host.gnogno
    1// Package memba_weighted_host hosts the founding policy's governed role actions.2// This source-only component is not the current memba_dao and grants no external3// application authority. The owning realm must keep its Host private.4package memba_weighted_host56import (7	"chain/runtime"

    Functions

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

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

    8 "strconv"
    9 "strings"
    10 "time"
    11
    12 "gno.land/p/samcrew/memba_weighted_policy"
    13)
    14
    15type Role string
    16
    17const (
    18 Admin Role = "admin"
    19 Finance Role = "finance"
    20)
    21
    22// Seat is copied at construction. Founder/core-developer voting eligibility is
    23// independent from these operational role labels; all members may execute.
    24type Seat struct {
    25 Member memba_weighted_policy.Member
    26 Admin bool
    27 Finance bool
    28}
    29
    30type roleProposal struct {
    31 feedback *feedbackProposal
    32 channels *channelsProposal
    33 feed *feedProposal
    34 badges *badgesProposal
    35 escrow *escrowProposal
    36 appstore *appstoreProposal
    37 arcade *arcadeProposal
    38 quest *questProposal
    39 reviews *reviewsProposal
    40 market *marketProposal
    41 recovery bool
    42 personID string
    43 replacement address
    44 proposer address
    45 target address
    46 role Role
    47 grant bool
    48 ballot *memba_weighted_policy.Proposal
    49 // roster indexes Host.rosters: the electorate frozen at creation. choices
    50 // and votedAt mirror each seat's current ballot and the height it was cast;
    51 // they are written only by Host.Vote, after the policy accepted the vote.
    52 roster int
    53 choices [7]memba_weighted_policy.Vote
    54 votedAt [7]int64
    55 // epoch is len(Host.invalidations) at creation: the first reconfiguration
    56 // recorded after it is the one that invalidated this proposal.
    57 epoch int
    58}
    59
    60// Invalidation causes recorded on every policy reconfiguration.
    61const (
    62 InvalidatedByExecution = "superseded-execution"
    63 InvalidatedByPause = "pause"
    64)
    65
    66// invalidation records one policy reconfiguration: every proposal open at that
    67// moment, other than the one executed, stopped accepting votes and execution.
    68type invalidation struct {
    69 height int64
    70 at time.Time
    71 cause string
    72 proposal uint64
    73 target string
    74}
    75
    76type Host struct {
    77 feedbackPolicy *FeedbackPolicy
    78 channelsPolicy *ChannelsPolicy
    79 feedPolicy *FeedPolicy
    80 badgesPolicy *BadgesPolicy
    81 escrowPolicy *EscrowPolicy
    82 appstorePolicy *AppstorePolicy
    83 arcadePolicy *ArcadePolicy
    84 questPolicy *QuestPolicy
    85 reviewsPolicy *ReviewsPolicy
    86 marketPolicy *MarketPolicy
    87 path string
    88 seats [7]Seat
    89 policy *memba_weighted_policy.Policy
    90 proposals []*roleProposal
    91 // rosters holds every distinct electorate, oldest first. A new entry is
    92 // appended only when a recovery changes a seat address.
    93 rosters [][7]address
    94 // invalidations mirrors the policy generation: one record per Reconfigure.
    95 invalidations []invalidation
    96}
    97
    98// New must run inside the owning realm's init with its own live realm value.
    99// No realm value is persisted; each mutation requires a fresh live frame.
    100func New(seats []Seat, rlm realm) *Host {
    101 if !rlm.IsCurrent() || !strings.HasPrefix(rlm.PkgPath(), "gno.land/r/") {
    102 panic("host requires its owning live realm")
    103 }
    104 members := []memba_weighted_policy.Member{}
    105 admins := 0
    106 for _, seat := range seats {
    107 members = append(members, seat.Member)
    108 if seat.Admin {
    109 admins++
    110 }
    111 }
    112 policy := memba_weighted_policy.New(members)
    113 if admins == 0 {
    114 panic("host requires an admin")
    115 }
    116 h := &Host{path: rlm.PkgPath(), policy: policy}
    117 for i, seat := range seats {
    118 h.seats[i] = seat
    119 }
    120 h.snapshotRoster()
    121 return h
    122}
    123
    124func (h *Host) snapshotRoster() {
    125 var roster [7]address
    126 for i, seat := range h.seats {
    127 roster[i] = seat.Member.Address
    128 }
    129 h.rosters = append(h.rosters, roster)
    130}
    131
    132// addProposal freezes the current electorate on the proposal and stores it.
    133// Every Propose* path goes through here, so ballots always have a snapshot.
    134func (h *Host) addProposal(p *roleProposal) {
    135 p.roster = len(h.rosters) - 1
    136 p.epoch = len(h.invalidations)
    137 h.proposals = append(h.proposals, p)
    138}
    139
    140// recordInvalidation must follow every policy Reconfigure. It records why the
    141// open proposals stopped; it never decides which ones stop.
    142func (h *Host) recordInvalidation(cause string, id uint64, target string) {
    143 h.invalidations = append(h.invalidations, invalidation{height: runtime.ChainHeight(), at: time.Now(), cause: cause, proposal: id, target: target})
    144}
    145
    146// ballotsQualified recomputes the policy threshold from the mirrored ballots.
    147// Ballots cannot change after a proposal closes or is invalidated, so this is
    148// the qualification the proposal had at that moment.
    149func (h *Host) ballotsQualified(p *roleProposal, category memba_weighted_policy.Category) bool {
    150 weight, people, developers := 0, 0, 0
    151 for i, vote := range p.choices {
    152 if vote != memba_weighted_policy.Yes {
    153 continue
    154 }
    155 people++
    156 weight++
    157 if h.seats[i].Member.Founder {
    158 weight++
    159 } else {
    160 developers++
    161 }
    162 }
    163 switch category {
    164 case memba_weighted_policy.Routine:
    165 return weight >= 3 && people >= 2
    166 case memba_weighted_policy.Financial:
    167 return weight >= 5 && people >= 3
    168 default:
    169 return weight >= 6 && people >= 4 || developers >= 5
    170 }
    171}
    172
    173func (h *Host) memberIndex(who address) int {
    174 for i, seat := range h.seats {
    175 if seat.Member.Address == who {
    176 return i
    177 }
    178 }
    179 return -1
    180}
    181
    182// Require both the owning path and its currently executing frame. A callee
    183// holding a previous DAO frame may not donate it back for authentication.
    184func assertCaller(h *Host, rlm realm) address {
    185 if rlm.PkgPath() != h.path || !rlm.IsCurrent() {
    186 panic("host realm mismatch")
    187 }
    188 who := rlm.Previous().Address()
    189 if !who.IsValid() || h.memberIndex(who) < 0 {
    190 panic("caller is not a member")
    191 }
    192 return who
    193}
    194
    195func (h *Host) HasRole(who address, role Role) bool {
    196 i := h.memberIndex(who)
    197 if i < 0 {
    198 return false
    199 }
    200 switch role {
    201 case Admin:
    202 return h.seats[i].Admin
    203 case Finance:
    204 return h.seats[i].Finance
    205 default:
    206 return false
    207 }
    208}
    209
    210func (h *Host) validateRoleChange(target address, role Role, grant bool) {
    211 if h.memberIndex(target) < 0 {
    212 panic("role target is not a member")
    213 }
    214 if role != Admin && role != Finance {
    215 panic("unsupported role")
    216 }
    217 if h.HasRole(target, role) == grant {
    218 panic("role change is a no-op")
    219 }
    220 if role == Admin && !grant {
    221 admins := 0
    222 for _, seat := range h.seats {
    223 if seat.Admin {
    224 admins++
    225 }
    226 }
    227 if admins <= 1 {
    228 panic("cannot remove last admin")
    229 }
    230 }
    231}
    232
    233// Every role change is critical. The proposer cannot select a lower category,
    234// inject a callback or substitute the target/role/grant after voting begins.
    235func (h *Host) ProposeRole(target address, role Role, grant bool, rlm realm) uint64 {
    236 who := assertCaller(h, rlm)
    237 h.validateRoleChange(target, role, grant)
    238 p := &roleProposal{proposer: who, target: target, role: role, grant: grant}
    239 p.ballot = h.policy.NewProposal(memba_weighted_policy.Critical, actionBytes(p))
    240 h.addProposal(p)
    241 return uint64(len(h.proposals))
    242}
    243
    244// Recovery replaces one person's key, never their identity, weight or roles.
    245func (h *Host) validateRecovery(personID string, target, replacement address) {
    246 i := h.memberIndex(target)
    247 if i < 0 || h.seats[i].Member.PersonID != personID {
    248 panic("recovery seat does not match")
    249 }
    250 if !replacement.IsValid() || h.memberIndex(replacement) >= 0 {
    251 panic("replacement must be a valid unused address")
    252 }
    253}
    254
    255func (h *Host) ProposeRecovery(personID string, target, replacement address, rlm realm) uint64 {
    256 who := assertCaller(h, rlm)
    257 h.validateRecovery(personID, target, replacement)
    258 p := &roleProposal{proposer: who, recovery: true, personID: personID, target: target, replacement: replacement}
    259 p.ballot = h.policy.NewProposal(memba_weighted_policy.Critical, actionBytes(p))
    260 h.addProposal(p)
    261 return uint64(len(h.proposals))
    262}
    263
    264func actionBytes(p *roleProposal) string {
    265 if p.recovery {
    266 // Length-prefix the unrestricted human ID to make the commitment unambiguous.
    267 return "recover/v1|" + strconv.Itoa(len(p.personID)) + ":" + p.personID + "|" + string(p.target) + "|" + string(p.replacement)
    268 }
    269 // Address and role domains are validated and contain no delimiter.
    270 return "role/v1|" + string(p.target) + "|" + string(p.role) + "|" + strconv.FormatBool(p.grant)
    271}
    272
    273func (h *Host) proposal(id uint64) *roleProposal {
    274 if id == 0 || id > uint64(len(h.proposals)) {
    275 panic("proposal not found")
    276 }
    277 return h.proposals[id-1]
    278}
    279
    280func (h *Host) Vote(id uint64, vote memba_weighted_policy.Vote, rlm realm) {
    281 who := assertCaller(h, rlm)
    282 p := h.proposal(id)
    283 p.ballot.Vote(who, vote)
    284 // The policy accepted the ballot, so the proposal is active and its frozen
    285 // electorate is the current roster. A repeated identical ballot is a no-op
    286 // in the policy and keeps its original height here.
    287 i := rosterIndex(h.rosters[p.roster], who)
    288 if i < 0 {
    289 panic("voter is not in the proposal electorate")
    290 }
    291 if p.choices[i] != vote {
    292 p.choices[i] = vote
    293 p.votedAt[i] = runtime.ChainHeight()
    294 }
    295}
    296
    297func rosterIndex(roster [7]address, who address) int {
    298 for i, a := range roster {
    299 if a == who {
    300 return i
    301 }
    302 }
    303 return -1
    304}
    305
    306func (h *Host) Execute(id uint64, rlm realm) {
    307 assertCaller(h, rlm)
    308 p := h.proposal(id)
    309 if p.feedback != nil {
    310 panic("feedback action requires target adapter")
    311 }
    312 if p.channels != nil {
    313 panic("channels action requires target adapter")
    314 }
    315 if p.feed != nil {
    316 panic("feed action requires target adapter")
    317 }
    318 if p.badges != nil {
    319 panic("badges action requires target adapter")
    320 }
    321 if p.escrow != nil {
    322 panic("escrow action requires target adapter")
    323 }
    324 if p.appstore != nil {
    325 panic("appstore action requires target adapter")
    326 }
    327 if p.arcade != nil {
    328 panic("arcade action requires target adapter")
    329 }
    330 if p.quest != nil {
    331 panic("quest action requires target adapter")
    332 }
    333 if p.reviews != nil {
    334 panic("reviews action requires target adapter")
    335 }
    336 if p.market != nil {
    337 panic("market action requires target adapter")
    338 }
    339 if p.recovery {
    340 h.validateRecovery(p.personID, p.target, p.replacement)
    341 } else {
    342 h.validateRoleChange(p.target, p.role, p.grant)
    343 }
    344 p.ballot.Consume(actionBytes(p))
    345 i := h.memberIndex(p.target)
    346 if p.recovery {
    347 h.seats[i].Member.Address = p.replacement
    348 h.snapshotRoster()
    349 } else if p.role == Admin {
    350 h.seats[i].Admin = p.grant
    351 } else {
    352 h.seats[i].Finance = p.grant
    353 }
    354 // Any changed authority invalidates all outstanding authorizations. Roles
    355 // do not alter the approved weights or the independent developer route.
    356 members := []memba_weighted_policy.Member{}
    357 for _, seat := range h.seats {
    358 members = append(members, seat.Member)
    359 }
    360 h.policy.Reconfigure(members)
    361 h.recordInvalidation(InvalidatedByExecution, id, "")
    362}
    363
    364// Views contain copied values only, never pointers to policy, ballots or seats.
    365type ProposalView struct {
    366 Feedback bool
    367 FeedbackAction FeedbackAction
    368 FeedbackBefore FeedbackState
    369 Channels bool
    370 ChannelsAction ChannelsAction
    371 ChannelsBefore ChannelsState
    372 Feed bool
    373 FeedAction FeedAction
    374 FeedBefore FeedState
    375 Badges bool
    376 BadgesAction BadgesAction
    377 BadgesBefore BadgesState
    378 Escrow bool
    379 EscrowAction EscrowAction
    380 EscrowBefore EscrowState
    381 Appstore bool
    382 AppstoreAction AppstoreAction
    383 AppstoreBefore AppstoreState
    384 Arcade bool
    385 ArcadeAction ArcadeAction
    386 ArcadeBefore ArcadeState
    387 Quest bool
    388 QuestAction QuestAction
    389 QuestBefore QuestState
    390 Reviews bool
    391 ReviewsAction ReviewsAction
    392 ReviewsBefore ReviewsState
    393 Market bool
    394 MarketAction MarketAction
    395 MarketBefore MarketState
    396 Recovery bool
    397 PersonID string
    398 Replacement address
    399 ID uint64
    400 Proposer address
    401 Target address
    402 Role Role
    403 Grant bool
    404 State memba_weighted_policy.State
    405 // Invalidation is set only for an INVALIDATED proposal. A proposal whose
    406 // vote had already closed without qualifying stays EXPIRED instead.
    407 Invalidated bool
    408 InvalidationCause string
    409 InvalidationHeight int64
    410 InvalidationProposal uint64
    411 InvalidationTarget string
    412 ExpiredBeforeInvalidation bool
    413}
    414
    415func (h *Host) Proposal(id uint64) ProposalView {
    416 v := h.proposalView(id)
    417 p := h.proposal(id)
    418 s := v.State
    419 if s.Invalidated && !s.Consumed && p.epoch < len(h.invalidations) {
    420 rec := h.invalidations[p.epoch]
    421 if !rec.at.Before(s.VotingDeadline) && !h.ballotsQualified(p, s.Category) {
    422 v.ExpiredBeforeInvalidation = true
    423 } else {
    424 v.Invalidated = true
    425 v.InvalidationCause = rec.cause
    426 v.InvalidationHeight = rec.height
    427 v.InvalidationProposal = rec.proposal
    428 v.InvalidationTarget = rec.target
    429 }
    430 }
    431 return v
    432}
    433
    434func (h *Host) proposalView(id uint64) ProposalView {
    435 p := h.proposal(id)
    436 if p.feedback != nil {
    437 return ProposalView{Feedback: true, FeedbackAction: p.feedback.action, FeedbackBefore: p.feedback.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    438 }
    439 if p.channels != nil {
    440 return ProposalView{Channels: true, ChannelsAction: p.channels.action, ChannelsBefore: p.channels.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    441 }
    442 if p.feed != nil {
    443 return ProposalView{Feed: true, FeedAction: p.feed.action, FeedBefore: p.feed.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    444 }
    445 if p.badges != nil {
    446 return ProposalView{Badges: true, BadgesAction: p.badges.action, BadgesBefore: p.badges.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    447 }
    448 if p.escrow != nil {
    449 return ProposalView{Escrow: true, EscrowAction: p.escrow.action, EscrowBefore: p.escrow.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    450 }
    451 if p.appstore != nil {
    452 return ProposalView{Appstore: true, AppstoreAction: p.appstore.action, AppstoreBefore: p.appstore.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    453 }
    454 if p.arcade != nil {
    455 return ProposalView{Arcade: true, ArcadeAction: p.arcade.action, ArcadeBefore: p.arcade.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    456 }
    457 if p.quest != nil {
    458 return ProposalView{Quest: true, QuestAction: p.quest.action, QuestBefore: p.quest.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    459 }
    460 if p.reviews != nil {
    461 return ProposalView{Reviews: true, ReviewsAction: p.reviews.action, ReviewsBefore: p.reviews.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    462 }
    463 if p.market != nil {
    464 return ProposalView{Market: true, MarketAction: p.market.action, MarketBefore: p.market.before, ID: id, Proposer: p.proposer, State: p.ballot.State()}
    465 }
    466 return ProposalView{Recovery: p.recovery, PersonID: p.personID, Replacement: p.replacement, ID: id, Proposer: p.proposer, Target: p.target, Role: p.role, Grant: p.grant, State: p.ballot.State()}
    467}
    468
    469func (h *Host) Members() []Seat {
    470 out := []Seat{}
    471 for _, seat := range h.seats {
    472 out = append(out, seat)
    473 }
    474 return out
    475}
    476
    477func (h *Host) ProposalCount() uint64 { return uint64(len(h.proposals)) }
    478