quest_host.gno
4.88 Kb · 162 lines
1package memba_weighted_host
2
3import "gno.land/p/samcrew/memba_weighted_policy"
4
5// ApplicationPolicies is copied and validated by construction. No policy can
6// subsequently be attached or changed on an existing host.
7type ApplicationPolicies struct {
8 Feedback FeedbackPolicy
9 Channels ChannelsPolicy
10 Feed FeedPolicy
11 Badges BadgesPolicy
12 Escrow EscrowPolicy
13 Appstore AppstorePolicy
14 Arcade ArcadePolicy
15 Market MarketPolicy
16 Reviews ReviewsPolicy
17 Quest QuestPolicy
18}
19
20func NewWithApplications(seats []Seat, policies ApplicationPolicies, rlm realm) *Host {
21 h := New(seats, rlm)
22 if policies.Market != (MarketPolicy{}) {
23 h = NewWithMarket(seats, policies.Market, rlm)
24 }
25 if policies.Reviews != (ReviewsPolicy{}) {
26 h = attachReviews(h, policies.Reviews, rlm)
27 }
28 if policies.Quest != (QuestPolicy{}) {
29 p := NewQuestPolicy(policies.Quest.DAO(), policies.Quest.Successor())
30 if rlm.Address() != p.DAO() {
31 panic("quest policy DAO does not own this host")
32 }
33 h.questPolicy = &p
34 }
35 if policies.Arcade != (ArcadePolicy{}) {
36 p := NewArcadePolicy(policies.Arcade.DAO(), policies.Arcade.Successor())
37 if rlm.Address() != p.DAO() {
38 panic("arcade policy DAO does not own this host")
39 }
40 h.arcadePolicy = &p
41 }
42 if policies.Appstore != (AppstorePolicy{}) {
43 p := NewAppstorePolicy(policies.Appstore.DAO(), policies.Appstore.Successor(), policies.Appstore.Treasury())
44 if rlm.Address() != p.DAO() {
45 panic("appstore policy DAO does not own this host")
46 }
47 h.appstorePolicy = &p
48 }
49 if policies.Escrow != (EscrowPolicy{}) {
50 p := NewEscrowPolicy(policies.Escrow.DAO(), policies.Escrow.Successor())
51 if rlm.Address() != p.DAO() {
52 panic("escrow policy DAO does not own this host")
53 }
54 h.escrowPolicy = &p
55 }
56 if policies.Badges != (BadgesPolicy{}) {
57 p := NewBadgesPolicy(policies.Badges.DAO(), policies.Badges.Successor())
58 if rlm.Address() != p.DAO() {
59 panic("badges policy DAO does not own this host")
60 }
61 h.badgesPolicy = &p
62 }
63 if policies.Feed != (FeedPolicy{}) {
64 p := NewFeedPolicy(policies.Feed.DAO(), policies.Feed.Successor())
65 if rlm.Address() != p.DAO() {
66 panic("feed policy DAO does not own this host")
67 }
68 h.feedPolicy = &p
69 }
70 if policies.Channels != (ChannelsPolicy{}) {
71 p := NewChannelsPolicy(policies.Channels.DAO(), policies.Channels.Successor())
72 if rlm.Address() != p.DAO() {
73 panic("channels policy DAO does not own this host")
74 }
75 h.channelsPolicy = &p
76 }
77 if policies.Feedback != (FeedbackPolicy{}) {
78 p := NewFeedbackPolicy(policies.Feedback.DAO(), policies.Feedback.Successor())
79 if rlm.Address() != p.DAO() {
80 panic("feedback policy DAO does not own this host")
81 }
82 h.feedbackPolicy = &p
83 }
84 return h
85}
86
87type QuestState struct {
88 Owner, PendingOwner address
89 Signer string
90}
91type questProposal struct {
92 action QuestAction
93 before QuestState
94}
95
96func questCommitment(a QuestAction, before QuestState) string {
97 return a.Bytes() + "|before|" + questStateJSON(before)
98}
99func (h *Host) validateQuest(a QuestAction, before QuestState) {
100 if h.questPolicy == nil {
101 panic("quest actions are not configured")
102 }
103 a.Bytes()
104 p := h.questPolicy
105 if !before.Owner.IsValid() || (before.PendingOwner != "" && !before.PendingOwner.IsValid()) || (before.Signer != "" && !canonicalQuestKey(before.Signer)) {
106 panic("invalid quest pre-state")
107 }
108 if a.Kind() == QuestAcceptOwner {
109 if before.Owner == p.DAO() || before.PendingOwner != p.DAO() {
110 panic("DAO is not pending quest owner")
111 }
112 return
113 }
114 if before.Owner != p.DAO() {
115 panic("DAO is not quest owner")
116 }
117 if a.Kind() == QuestAbortReturn {
118 if a.Recipient() != p.Successor() || before.PendingOwner != p.Successor() {
119 panic("quest return does not match policy")
120 }
121 return
122 }
123 if before.PendingOwner != "" {
124 panic("quest authority transfer is pending")
125 }
126 switch a.Kind() {
127 case QuestSetSigner:
128 if before.Signer == a.Key() {
129 panic("quest signer change is a no-op")
130 }
131 case QuestReturnOwner:
132 if a.Recipient() != p.Successor() {
133 panic("quest successor does not match policy")
134 }
135 default:
136 panic("unsupported quest action")
137 }
138}
139func (h *Host) ProposeQuest(a QuestAction, before QuestState, rlm realm) uint64 {
140 who := assertCaller(h, rlm)
141 h.validateQuest(a, before)
142 p := &roleProposal{proposer: who, quest: &questProposal{action: a, before: before}}
143 p.ballot = h.policy.NewProposal(a.Category(), questCommitment(a, before))
144 h.addProposal(p)
145 return uint64(len(h.proposals))
146}
147func (h *Host) ConsumeQuest(id uint64, before QuestState, rlm realm) QuestAction {
148 assertCaller(h, rlm)
149 p := h.proposal(id)
150 if p.quest == nil {
151 panic("proposal is not a quest action")
152 }
153 h.validateQuest(p.quest.action, before)
154 p.ballot.Consume(questCommitment(p.quest.action, before))
155 members := []memba_weighted_policy.Member{}
156 for _, seat := range h.seats {
157 members = append(members, seat.Member)
158 }
159 h.policy.Reconfigure(members)
160 h.recordInvalidation(InvalidatedByExecution, id, QuestPath)
161 return p.quest.action
162}