market_host.gno
3.95 Kb · 119 lines
1package memba_weighted_host
2
3import (
4 "gno.land/p/samcrew/memba_weighted_policy"
5 "strconv"
6)
7
8// MarketState is copied from the fixed target by the owning realm adapter.
9// BPS is read only for a fee action; other actions commit zero in that field.
10type MarketState struct {
11 Admin address
12 PendingAdmin address
13 Treasury address
14 BPS int64
15}
16
17type marketProposal struct {
18 action MarketAction
19 before MarketState
20}
21
22// NewWithMarket binds immutable destinations. Keep the Host private; the realm
23// must expose only the reviewed fixed-target adapter, never ConsumeMarket.
24func NewWithMarket(seats []Seat, policy MarketPolicy, rlm realm) *Host {
25 policy = NewMarketPolicy(policy.DAO(), policy.Treasury(), policy.Successor())
26 if rlm.Address() != policy.DAO() {
27 panic("market policy DAO does not own this host")
28 }
29 h := New(seats, rlm)
30 h.marketPolicy = &policy
31 return h
32}
33
34func marketCommitment(action MarketAction, before MarketState) string {
35 if !before.Admin.IsValid() || !before.Treasury.IsValid() || (before.PendingAdmin != "" && !before.PendingAdmin.IsValid()) || before.BPS < 0 || before.BPS > 500 {
36 panic("invalid market pre-state")
37 }
38 if action.Kind() != MarketSetFee && before.BPS != 0 {
39 panic("unexpected market fee pre-state")
40 }
41 return action.Bytes() + "|before|" + string(before.Admin) + "|" + string(before.PendingAdmin) + "|" + string(before.Treasury) + "|" + strconv.FormatInt(before.BPS, 10)
42}
43
44func (h *Host) validateMarket(action MarketAction, before MarketState) {
45 if h.marketPolicy == nil {
46 panic("market actions are not configured")
47 }
48 marketCommitment(action, before)
49 p := h.marketPolicy
50 if action.Kind() == MarketAcceptAdmin {
51 if before.Admin == p.DAO() || before.PendingAdmin != p.DAO() {
52 panic("DAO is not pending market admin")
53 }
54 return
55 }
56 if before.Admin != p.DAO() {
57 panic("DAO is not market admin")
58 }
59 if action.Kind() == MarketAbortReturn {
60 if action.Recipient() != p.Successor() || before.PendingAdmin != p.Successor() {
61 panic("market return does not match policy")
62 }
63 return
64 }
65 if before.PendingAdmin != "" {
66 panic("market authority transfer is pending")
67 }
68 switch action.Kind() {
69 case MarketSetFee:
70 if action.BPS() == before.BPS {
71 panic("market fee change is a no-op")
72 }
73 case MarketSetTreasury:
74 if action.Recipient() != p.Treasury() {
75 panic("market treasury does not match policy")
76 }
77 if before.Treasury == p.Treasury() {
78 panic("market treasury change is a no-op")
79 }
80 case MarketReturnAdmin:
81 if action.Recipient() != p.Successor() {
82 panic("market successor does not match policy")
83 }
84 default:
85 panic("unsupported market action")
86 }
87}
88
89func (h *Host) ProposeMarket(action MarketAction, before MarketState, rlm realm) uint64 {
90 who := assertCaller(h, rlm)
91 h.validateMarket(action, before)
92 p := &roleProposal{proposer: who, market: &marketProposal{action: action, before: before}}
93 p.ballot = h.policy.NewProposal(action.Category(), marketCommitment(action, before))
94 h.addProposal(p)
95 return uint64(len(h.proposals))
96}
97
98// ConsumeMarket is for the owning realm's fixed adapter only. It checks the
99// fresh target state and consumes authorization BEFORE crossing. No recovery
100// is permitted in that adapter: a target panic must abort the transaction.
101func (h *Host) ConsumeMarket(id uint64, before MarketState, rlm realm) MarketAction {
102 assertCaller(h, rlm)
103 p := h.proposal(id)
104 if p.market == nil {
105 panic("proposal is not a market action")
106 }
107 h.validateMarket(p.market.action, before)
108 p.ballot.Consume(marketCommitment(p.market.action, before))
109 // A successful application action invalidates all other authorizations.
110 // An old approval cannot revive after a return/reaccept or intervening fee
111 // change. The realm lets dispatch failure abort this mutation too.
112 members := []memba_weighted_policy.Member{}
113 for _, seat := range h.seats {
114 members = append(members, seat.Member)
115 }
116 h.policy.Reconfigure(members)
117 h.recordInvalidation(InvalidatedByExecution, id, MarketConfigPath)
118 return p.market.action
119}