badges_host.gno
3.67 Kb · 124 lines
1package memba_weighted_host
2
3import "gno.land/p/samcrew/memba_weighted_policy"
4
5const BadgesMaxAdmins = 10
6
7// Admin bits are explicit grants. The current owner's authority to appoint is
8// separate from those bits, but target ownership always installs an admin grant.
9type BadgesState struct {
10 Owner, PendingOwner address
11 Paused bool
12 AdminCount int
13 OwnerAdmin, DAOAdmin, SuccessorAdmin, SubjectAdmin bool
14}
15type badgesProposal struct {
16 action BadgesAction
17 before BadgesState
18}
19
20func badgesCommitment(a BadgesAction, s BadgesState) string {
21 return a.Bytes() + "|before|" + badgesStateJSON(s)
22}
23func (h *Host) validateBadgesAuthority(s BadgesState) {
24 if h.badgesPolicy == nil {
25 panic("badges actions are not configured")
26 }
27 if !s.Owner.IsValid() || (s.PendingOwner != "" && !s.PendingOwner.IsValid()) || !s.OwnerAdmin || s.AdminCount < 1 || s.AdminCount > BadgesMaxAdmins {
28 panic("invalid badges authority pre-state")
29 }
30}
31func (h *Host) validateBadges(a BadgesAction, s BadgesState) {
32 h.validateBadgesAuthority(s)
33 a.Bytes()
34 p := h.badgesPolicy
35 if a.Subject() == "" && s.SubjectAdmin {
36 panic("unexpected badges subject pre-state")
37 }
38 if a.Kind() == BadgesAcceptOwner {
39 if s.Owner == p.DAO() || s.PendingOwner != p.DAO() {
40 panic("DAO is not pending badges owner")
41 }
42 return
43 }
44 if s.Owner != p.DAO() || !s.DAOAdmin {
45 panic("DAO is not badges owner/admin")
46 }
47 if a.Kind() == BadgesAbortReturn {
48 if a.Recipient() != p.Successor() || s.PendingOwner != p.Successor() {
49 panic("badges return does not match policy")
50 }
51 return
52 }
53 if s.PendingOwner != "" {
54 panic("badges authority transfer is pending")
55 }
56 switch a.Kind() {
57 case BadgesReturnOwner:
58 if a.Recipient() != p.Successor() {
59 panic("badges successor does not match policy")
60 }
61 case BadgesAddAdmin:
62 p.AddAdmin(a.Subject())
63 if s.SubjectAdmin {
64 panic("badges admin grant is a no-op")
65 }
66 if s.AdminCount >= BadgesMaxAdmins {
67 panic("badges admin limit reached")
68 }
69 case BadgesRemoveAdmin:
70 if a.Subject() == s.Owner {
71 panic("cannot remove badges owner admin")
72 }
73 if !s.SubjectAdmin {
74 panic("badges admin removal is a no-op")
75 }
76 case BadgesUnpause:
77 if !s.Paused {
78 panic("badges unpause is a no-op")
79 }
80 default:
81 panic("unsupported badges action")
82 }
83}
84func (h *Host) ProposeBadges(a BadgesAction, s BadgesState, rlm realm) uint64 {
85 who := assertCaller(h, rlm)
86 h.validateBadges(a, s)
87 p := &roleProposal{proposer: who, badges: &badgesProposal{action: a, before: s}}
88 p.ballot = h.policy.NewProposal(a.Category(), badgesCommitment(a, s))
89 h.addProposal(p)
90 return uint64(len(h.proposals))
91}
92func (h *Host) invalidateBadgesPeers(cause string, id uint64) {
93 members := []memba_weighted_policy.Member{}
94 for _, seat := range h.seats {
95 members = append(members, seat.Member)
96 }
97 h.policy.Reconfigure(members)
98 h.recordInvalidation(cause, id, BadgesPath)
99}
100func (h *Host) ConsumeBadges(id uint64, s BadgesState, rlm realm) BadgesAction {
101 assertCaller(h, rlm)
102 p := h.proposal(id)
103 if p.badges == nil {
104 panic("proposal is not a badges action")
105 }
106 h.validateBadges(p.badges.action, s)
107 p.ballot.Consume(badgesCommitment(p.badges.action, s))
108 h.invalidateBadgesPeers(InvalidatedByExecution, id)
109 return p.badges.action
110}
111func (h *Host) PrepareBadgesEmergencyPause(s BadgesState, rlm realm) {
112 assertCaller(h, rlm)
113 h.validateBadgesAuthority(s)
114 if s.Owner != h.badgesPolicy.DAO() || !s.DAOAdmin {
115 panic("DAO is not badges owner/admin")
116 }
117 if s.Paused {
118 panic("badges is already paused")
119 }
120 if s.SubjectAdmin {
121 panic("unexpected badges subject pre-state")
122 }
123 h.invalidateBadgesPeers(InvalidatedByPause, 0)
124}