arcade_host.gno
3.39 Kb · 116 lines
1package memba_weighted_host
2
3import "gno.land/p/samcrew/memba_weighted_policy"
4
5// Explicit allowlist bits are separate from the owner's implicit attester right.
6type ArcadeState struct {
7 Owner, PendingOwner address
8 Paused, OwnerListed, DAOListed, SuccessorListed, SubjectListed bool
9}
10type arcadeProposal struct {
11 action ArcadeAction
12 before ArcadeState
13}
14
15func arcadeCommitment(a ArcadeAction, s ArcadeState) string {
16 return a.Bytes() + "|before|" + arcadeStateJSON(s)
17}
18func (h *Host) validateArcadeAuthority(s ArcadeState) {
19 if h.arcadePolicy == nil {
20 panic("arcade actions are not configured")
21 }
22 if !s.Owner.IsValid() || (s.PendingOwner != "" && !s.PendingOwner.IsValid()) {
23 panic("invalid arcade authority pre-state")
24 }
25}
26func (h *Host) validateArcade(a ArcadeAction, s ArcadeState) {
27 h.validateArcadeAuthority(s)
28 a.Bytes()
29 p := h.arcadePolicy
30 if a.Attester() == "" && s.SubjectListed {
31 panic("unexpected arcade subject pre-state")
32 }
33 if a.Kind() == ArcadeAcceptOwner {
34 if s.Owner == p.DAO() || s.PendingOwner != p.DAO() {
35 panic("DAO is not pending arcade owner")
36 }
37 return
38 }
39 if s.Owner != p.DAO() {
40 panic("DAO is not arcade owner")
41 }
42 if a.Kind() == ArcadeAbortReturn {
43 if a.Recipient() != p.Successor() || s.PendingOwner != p.Successor() {
44 panic("arcade return does not match policy")
45 }
46 return
47 }
48 if s.PendingOwner != "" {
49 panic("arcade authority transfer is pending")
50 }
51 switch a.Kind() {
52 case ArcadeReturnOwner:
53 if a.Recipient() != p.Successor() {
54 panic("arcade successor does not match policy")
55 }
56 case ArcadeAddAttester:
57 p.AddAttester(a.Attester())
58 if s.SubjectListed {
59 panic("arcade attester grant is a no-op")
60 }
61 case ArcadeRemoveAttester:
62 if !s.SubjectListed {
63 panic("arcade attester removal is a no-op")
64 }
65 case ArcadeUnpause:
66 if !s.Paused {
67 panic("arcade unpause is a no-op")
68 }
69 default:
70 panic("unsupported arcade action")
71 }
72}
73func (h *Host) ProposeArcade(a ArcadeAction, s ArcadeState, rlm realm) uint64 {
74 who := assertCaller(h, rlm)
75 h.validateArcade(a, s)
76 p := &roleProposal{proposer: who, arcade: &arcadeProposal{action: a, before: s}}
77 p.ballot = h.policy.NewProposal(a.Category(), arcadeCommitment(a, s))
78 h.addProposal(p)
79 return uint64(len(h.proposals))
80}
81func (h *Host) invalidateArcadePeers(cause string, id uint64) {
82 members := []memba_weighted_policy.Member{}
83 for _, seat := range h.seats {
84 members = append(members, seat.Member)
85 }
86 h.policy.Reconfigure(members)
87 h.recordInvalidation(cause, id, ArcadePath)
88}
89func (h *Host) ConsumeArcade(id uint64, s ArcadeState, rlm realm) ArcadeAction {
90 assertCaller(h, rlm)
91 p := h.proposal(id)
92 if p.arcade == nil {
93 panic("proposal is not an arcade action")
94 }
95 h.validateArcade(p.arcade.action, s)
96 p.ballot.Consume(arcadeCommitment(p.arcade.action, s))
97 h.invalidateArcadePeers(InvalidatedByExecution, id)
98 return p.arcade.action
99}
100
101// A fixed pause-only capability, not a proposal category or a bool-controlled
102// escape. An already-paused call cannot invalidate pending unpause approvals.
103func (h *Host) PrepareArcadeEmergencyPause(s ArcadeState, rlm realm) {
104 assertCaller(h, rlm)
105 h.validateArcadeAuthority(s)
106 if s.Owner != h.arcadePolicy.DAO() {
107 panic("DAO is not arcade owner")
108 }
109 if s.Paused {
110 panic("arcade is already paused")
111 }
112 if s.SubjectListed {
113 panic("unexpected arcade subject pre-state")
114 }
115 h.invalidateArcadePeers(InvalidatedByPause, 0)
116}