appstore_host.gno
5.00 Kb · 170 lines
1package memba_weighted_host
2
3import "gno.land/p/samcrew/memba_weighted_policy"
4
5type AppstoreListing struct {
6 Exists bool
7 ContentHash, Status, Reason string
8 Credit bool
9 Flags int
10 ClearKeys string
11}
12type AppstoreState struct {
13 Owner, PendingOwner, Treasury address
14 Fee int64
15 Paused, Sealed, OwnerCurator, DAOCurator, SuccessorCurator, SubjectCurator bool
16 Listing AppstoreListing
17}
18type appstoreProposal struct {
19 action AppstoreAction
20 before AppstoreState
21}
22
23func appstoreCommitment(a AppstoreAction, s AppstoreState) string {
24 return a.Bytes() + "|before|" + appstoreStateJSON(s)
25}
26func (h *Host) validateAppstoreAuthority(s AppstoreState) {
27 if h.appstorePolicy == nil {
28 panic("appstore actions are not configured")
29 }
30 if !s.Owner.IsValid() || !s.Treasury.IsValid() || (s.PendingOwner != "" && !s.PendingOwner.IsValid()) || s.Fee < 0 || s.Fee > AppstoreMaxFee {
31 panic("invalid appstore pre-state")
32 }
33}
34func (h *Host) validateAppstore(a AppstoreAction, s AppstoreState) {
35 h.validateAppstoreAuthority(s)
36 a.Bytes()
37 p := h.appstorePolicy
38 if !a.IsListing() && s.Listing != (AppstoreListing{}) {
39 panic("unexpected appstore listing state")
40 }
41 if a.kind != AppstoreAddCurator && a.kind != AppstoreRemoveCurator && s.SubjectCurator {
42 panic("unexpected appstore curator state")
43 }
44 if a.kind == AppstoreAccept {
45 if s.Owner == p.DAO() || s.PendingOwner != p.DAO() {
46 panic("DAO is not pending appstore owner")
47 }
48 return
49 }
50 if s.Owner != p.DAO() {
51 panic("DAO is not appstore owner")
52 }
53 if a.kind == AppstoreAbortReturn {
54 if a.recipient != p.Successor() || s.PendingOwner != p.Successor() {
55 panic("appstore return does not match policy")
56 }
57 return
58 }
59 if s.PendingOwner != "" {
60 panic("appstore authority transfer is pending")
61 }
62 if a.IsListing() {
63 l := s.Listing
64 if !s.DAOCurator {
65 panic("DAO is not appstore curator")
66 }
67 if !l.Exists || l.ContentHash == "" || l.Flags < 0 {
68 panic("invalid appstore listing")
69 }
70 switch a.kind {
71 case AppstoreApprove:
72 if l.Status != "pending" && l.Status != "rejected" {
73 panic("appstore approval requires pending or rejected listing")
74 }
75 case AppstoreReject:
76 if l.Status != "pending" {
77 panic("appstore rejection requires pending listing")
78 }
79 case AppstoreDelist:
80 if l.Status == "delisted" {
81 panic("appstore delist is a no-op")
82 }
83 case AppstoreRestore:
84 if l.Status != "delisted" {
85 panic("appstore restore requires delisted listing")
86 }
87 case AppstoreClearFlags:
88 if l.Flags <= 0 || l.ClearKeys == "" {
89 panic("appstore has no removable flags")
90 }
91 }
92 return
93 }
94 switch a.kind {
95 case AppstoreReturn:
96 if a.recipient != p.Successor() {
97 panic("appstore successor does not match policy")
98 }
99 case AppstoreTreasury:
100 if a.recipient != p.Treasury() {
101 panic("appstore treasury does not match policy")
102 }
103 if s.Treasury == a.recipient {
104 panic("appstore treasury is a no-op")
105 }
106 case AppstoreFee:
107 if s.Fee == a.fee {
108 panic("appstore fee is a no-op")
109 }
110 case AppstoreAddCurator:
111 if s.SubjectCurator {
112 panic("appstore curator grant is a no-op")
113 }
114 case AppstoreRemoveCurator:
115 if !s.SubjectCurator {
116 panic("appstore curator removal is a no-op")
117 }
118 case AppstoreUnpause:
119 if !s.Paused {
120 panic("appstore unpause is a no-op")
121 }
122 case AppstoreSeal:
123 if s.Sealed {
124 panic("appstore import already sealed")
125 }
126 default:
127 panic("unsupported appstore action")
128 }
129}
130func (h *Host) ProposeAppstore(a AppstoreAction, s AppstoreState, rlm realm) uint64 {
131 who := assertCaller(h, rlm)
132 h.validateAppstore(a, s)
133 p := &roleProposal{proposer: who, appstore: &appstoreProposal{action: a, before: s}}
134 p.ballot = h.policy.NewProposal(a.Category(), appstoreCommitment(a, s))
135 h.addProposal(p)
136 return uint64(len(h.proposals))
137}
138func (h *Host) invalidateAppstorePeers(cause string, id uint64) {
139 members := []memba_weighted_policy.Member{}
140 for _, seat := range h.seats {
141 members = append(members, seat.Member)
142 }
143 h.policy.Reconfigure(members)
144 h.recordInvalidation(cause, id, AppstorePath)
145}
146func (h *Host) ConsumeAppstore(id uint64, s AppstoreState, rlm realm) AppstoreAction {
147 assertCaller(h, rlm)
148 p := h.proposal(id)
149 if p.appstore == nil {
150 panic("proposal is not an appstore action")
151 }
152 h.validateAppstore(p.appstore.action, s)
153 p.ballot.Consume(appstoreCommitment(p.appstore.action, s))
154 h.invalidateAppstorePeers(InvalidatedByExecution, id)
155 return p.appstore.action
156}
157func (h *Host) PrepareAppstoreEmergencyPause(s AppstoreState, rlm realm) {
158 assertCaller(h, rlm)
159 h.validateAppstoreAuthority(s)
160 if s.Owner != h.appstorePolicy.DAO() {
161 panic("DAO is not appstore owner")
162 }
163 if s.Paused {
164 panic("appstore is already paused")
165 }
166 if s.Listing != (AppstoreListing{}) || s.SubjectCurator {
167 panic("unexpected appstore pause state")
168 }
169 h.invalidateAppstorePeers(InvalidatedByPause, 0)
170}