Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

reviews_host.gno

4.75 Kb · 155 lines
  1package memba_weighted_host
  2
  3import "gno.land/p/samcrew/memba_weighted_policy"
  4
  5// ReviewsItem contains copied moderation-relevant state only. Ordinary reactions
  6// do not invalidate moderation; content edits, deletion and visibility do.
  7type ReviewsItem struct {
  8	ID        uint64
  9	Review    bool
 10	ParentID  uint64
 11	Subject   string
 12	Author    address
 13	Rating    int
 14	BodyHash  string
 15	CreatedAt int64
 16	EditedAt  int64
 17	Hidden    bool
 18	Deleted   bool
 19	Flagged   bool
 20}
 21type ReviewsState struct {
 22	Moderator        address
 23	PendingModerator address
 24	Item             ReviewsItem
 25}
 26type reviewsProposal struct {
 27	action ReviewsAction
 28	before ReviewsState
 29}
 30
 31func attachReviews(h *Host, policy ReviewsPolicy, rlm realm) *Host {
 32	policy = NewReviewsPolicy(policy.DAO(), policy.Target(), policy.Successor())
 33	if rlm.Address() != policy.DAO() {
 34		panic("reviews policy DAO does not own this host")
 35	}
 36	h.reviewsPolicy = &policy
 37	return h
 38}
 39func NewWithReviews(seats []Seat, policy ReviewsPolicy, rlm realm) *Host {
 40	return attachReviews(New(seats, rlm), policy, rlm)
 41}
 42func NewWithMarketAndReviews(seats []Seat, market MarketPolicy, reviews ReviewsPolicy, rlm realm) *Host {
 43	return attachReviews(NewWithMarket(seats, market, rlm), reviews, rlm)
 44}
 45func reviewsCommitment(action ReviewsAction, before ReviewsState) string {
 46	return action.Bytes() + "|before|" + reviewsStateJSON(before)
 47}
 48func (h *Host) validateReviews(a ReviewsAction, before ReviewsState) {
 49	if h.reviewsPolicy == nil {
 50		panic("reviews actions are not configured")
 51	}
 52	a.Bytes()
 53	p := h.reviewsPolicy
 54	if a.Target() != p.Target() {
 55		panic("reviews target does not match policy")
 56	}
 57	if !before.Moderator.IsValid() || (before.PendingModerator != "" && !before.PendingModerator.IsValid()) {
 58		panic("invalid reviews authority pre-state")
 59	}
 60	if a.ID() == 0 && before.Item != (ReviewsItem{}) {
 61		panic("unexpected reviews item pre-state")
 62	}
 63	if a.Kind() == ReviewsAcceptModerator {
 64		if before.Moderator == p.DAO() || before.PendingModerator != p.DAO() {
 65			panic("DAO is not pending reviews moderator")
 66		}
 67		return
 68	}
 69	if before.Moderator != p.DAO() {
 70		panic("DAO is not reviews moderator")
 71	}
 72	if a.Kind() == ReviewsAbortReturn {
 73		if a.Recipient() != p.Successor() || before.PendingModerator != p.Successor() {
 74			panic("reviews return does not match policy")
 75		}
 76		return
 77	}
 78	if before.PendingModerator != "" {
 79		panic("reviews authority transfer is pending")
 80	}
 81	if a.Kind() == ReviewsReturnModerator {
 82		if a.Recipient() != p.Successor() {
 83			panic("reviews successor does not match policy")
 84		}
 85		return
 86	}
 87	item := before.Item
 88	if item.ID != a.ID() || item.ID == 0 {
 89		panic("reviews item not found")
 90	}
 91	// Legacy v1 retains flagged-index entries after deletion. Unhide may
 92	// clear only such a flagged tombstone; the adapter preserves Deleted.
 93	if item.Deleted && !(a.Kind() == ReviewsUnhide && item.Flagged) {
 94		panic("reviews item is deleted")
 95	}
 96	if !item.Author.IsValid() || len(item.BodyHash) != 64 {
 97		panic("invalid reviews item pre-state")
 98	}
 99	if item.Review {
100		if item.ParentID != 0 || item.Subject == "" || item.Rating < 1 || item.Rating > 5 {
101			panic("invalid review pre-state")
102		}
103	} else if item.ParentID == 0 || item.Subject != "" || item.Rating != 0 {
104		panic("invalid comment pre-state")
105	}
106	switch a.Kind() {
107	case ReviewsHideReview:
108		if !item.Review {
109			panic("reviews item is not a review")
110		}
111		if item.Hidden {
112			panic("reviews hide is a no-op")
113		}
114	case ReviewsHideComment:
115		if item.Review {
116			panic("reviews item is not a comment")
117		}
118		if item.Hidden {
119			panic("reviews hide is a no-op")
120		}
121	case ReviewsUnhide:
122		if !item.Hidden && !item.Flagged {
123			panic("reviews unhide is a no-op")
124		}
125	default:
126		panic("unsupported reviews action")
127	}
128}
129func (h *Host) ProposeReviews(action ReviewsAction, before ReviewsState, rlm realm) uint64 {
130	who := assertCaller(h, rlm)
131	h.validateReviews(action, before)
132	p := &roleProposal{proposer: who, reviews: &reviewsProposal{action: action, before: before}}
133	p.ballot = h.policy.NewProposal(action.Category(), reviewsCommitment(action, before))
134	h.addProposal(p)
135	return uint64(len(h.proposals))
136}
137
138// Only the owning realm's fixed adapter may use this. Keep Host private and
139// let target panics/postcondition failures abort the entire transaction.
140func (h *Host) ConsumeReviews(id uint64, before ReviewsState, rlm realm) ReviewsAction {
141	assertCaller(h, rlm)
142	p := h.proposal(id)
143	if p.reviews == nil {
144		panic("proposal is not a reviews action")
145	}
146	h.validateReviews(p.reviews.action, before)
147	p.ballot.Consume(reviewsCommitment(p.reviews.action, before))
148	members := []memba_weighted_policy.Member{}
149	for _, seat := range h.seats {
150		members = append(members, seat.Member)
151	}
152	h.policy.Reconfigure(members)
153	h.recordInvalidation(InvalidatedByExecution, id, h.reviewsPolicy.Target())
154	return p.reviews.action
155}