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

gov.gno

7.10 Kb · 212 lines
  1package memba_notes_v1
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"crypto/sha256"
  7	"encoding/binary"
  8	"encoding/hex"
  9	"gno.land/p/samcrew/daoauth"
 10	gov "gno.land/r/samcrew/memba_gov"
 11	"strconv"
 12)
 13
 14const GovRealmPath = "gno.land/r/samcrew/memba_gov"
 15
 16var govAddress = chain.PackageAddress(GovRealmPath)
 17
 18func govMember(who address) bool { return gov.IsMember(who) }
 19func SetGovWriters(cur realm, id [16]byte, baseRevision uint64, on bool, opID [16]byte) {
 20	who := enter(cur, false)
 21	defer leave()
 22	mutateSetGovWriters(cur, who, id, baseRevision, on, opID)
 23}
 24func mutateSetGovWriters(cur realm, who address, id [16]byte, baseRevision uint64, on bool, opID [16]byte) {
 25	n := live(id)
 26	owner(n, who)
 27	expected(n, baseRevision)
 28	if n.Owner != govAddress {
 29		panic("notes: gov owner required")
 30	}
 31	operation(n, opID)
 32	n.GovWriters = on
 33	bump(n, who, opID, "gov-writers")
 34}
 35func validGovAction(action string) bool {
 36	switch action {
 37	case "AcceptOwnership", "Commit", "Rename", "Rotate", "ApplyAccess", "Publish", "Reveal", "Delete", "AddWriter", "RemoveWriter", "ProposeOwner", "SetPolicy", "SetCommentMode", "SetPublicWrites", "SetListed", "ModerateListing", "SetGovWriters", "HideComment", "ResolveComment", "RejectAccessRequest", "Purge":
 38		return true
 39	}
 40	return false
 41}
 42
 43// The typed payload is bounded and hashed as bytes, including its version.
 44// Every precondition and opID is domain-bound; no raw large/Unicode content is
 45// put in daoauth's 1024-byte printable-ASCII argument envelope.
 46func govDigest(n *note, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) string {
 47	if !validGovAction(action) || len(payload) < 1 || len(payload) > MaxGovPayloadBytes {
 48		panic("notes: unknown/oversize gov operation")
 49	}
 50	b := appendText(nil, "memba-notes/v1/gov-operation")
 51	b = appendText(b, runtime.ChainID())
 52	b = appendText(b, RealmPath)
 53	b = append(b, n.ID[:]...)
 54	number := make([]byte, 8)
 55	binary.BigEndian.PutUint64(number, ownerGeneration)
 56	b = append(b, number...)
 57	binary.BigEndian.PutUint64(number, baseRevision)
 58	b = append(b, number...)
 59	ep := make([]byte, 4)
 60	binary.BigEndian.PutUint32(ep, expectedEpoch)
 61	b = append(b, ep...)
 62	b = append(b, n.Mode)
 63	b = appendText(b, string(n.Owner))
 64	b = appendText(b, string(n.PendingOwner))
 65	b = appendText(b, string(n.Commitment))
 66	b = appendText(b, action)
 67	hash := sha256.Sum256(payload)
 68	b = append(b, hash[:]...)
 69	b = append(b, opID[:]...)
 70	digest := sha256.Sum256(b)
 71	return hex.EncodeToString(digest[:])
 72}
 73func GovDigest(idHex string, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) string {
 74	return govDigest(find(readID(idHex)), baseRevision, ownerGeneration, expectedEpoch, action, payload, opID)
 75}
 76func GovApply(cur realm, pid uint64, id [16]byte, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) {
 77	executor := enter(cur, false)
 78	defer leave()
 79	if !cur.Previous().IsUserCall() {
 80		panic("notes: direct governance executor required")
 81	}
 82	operation(nil, opID)
 83	n := find(id)
 84	expected(n, baseRevision)
 85	if ownerGeneration != n.OwnerGeneration || expectedEpoch != n.Epoch {
 86		panic("notes: stale governance generation/epoch")
 87	}
 88	if action == "AcceptOwnership" {
 89		if n.PendingOwner != govAddress {
 90			panic("notes: pending gov owner required")
 91		}
 92	} else if n.Owner != govAddress && !((action == "ModerateListing" || action == "HideComment") && moderator(govAddress)) {
 93		panic("notes: gov owner required")
 94	}
 95	digest := govDigest(n, baseRevision, ownerGeneration, expectedEpoch, action, payload, opID)
 96	// Failure in dispatch also rolls back Consume in the same transaction.
 97	gov.Consume(cross(cur), pid, "notes."+action, daoauth.New().Str(digest).String(), "note:"+idText(id), gov.Routine, executor)
 98	historyGov = &historyCall{executor, pid, opID, action}
 99	defer func() { historyGov = nil }()
100	dispatchGov(cur, n, baseRevision, expectedEpoch, action, payload, opID)
101	if n.Revision > baseRevision && n.Last.Operation == opID {
102		n.LastExecutor = executor
103	}
104	chain.Emit("NoteGoverned", "id", idText(id), "action", action, "pid", strconv.FormatUint(pid, 10), "executor", string(executor), "op_id", idText(opID))
105}
106func dispatchGov(cur realm, n *note, rev uint64, epoch uint32, action string, payload []byte, op [16]byte) {
107	r := payloadReader(payload)
108	id := n.ID
109	who := govAddress
110	switch action {
111	case "AcceptOwnership":
112		r.done()
113		mutateAcceptOwnership(cur, who, id, rev, op)
114	case "Commit":
115		mask := r.byte()
116		title := r.blob(MaxCipherTitleBytes)
117		body := r.blob(MaxCipherBodyBytes)
118		r.done()
119		mutateCommit(cur, who, id, rev, epoch, mask, title, body, op)
120	case "Rename":
121		title := r.blob(MaxCipherTitleBytes)
122		r.done()
123		mutateCommit(cur, who, id, rev, epoch, 1, title, nil, op)
124	case "Rotate":
125		mode := r.byte()
126		title := r.blob(MaxCipherTitleBytes)
127		body := r.blob(MaxCipherBodyBytes)
128		manifest := r.blob(MaxManifestBytes)
129		kc := r.blob(32)
130		r.done()
131		mutateRotate(cur, who, id, rev, epoch, mode, title, body, manifest, kc, op)
132	case "ApplyAccess":
133		mode := r.byte()
134		manifest := r.blob(MaxManifestBytes)
135		r.done()
136		mutateApplyAccess(cur, who, id, rev, epoch, mode, manifest, op)
137	case "Publish":
138		mode := r.byte()
139		title := r.blob(MaxTitleBytes)
140		body := r.blob(MaxBodyBytes)
141		r.done()
142		mutatePublish(cur, who, id, rev, mode, title, body, op)
143	case "Reveal":
144		ep := r.u32()
145		key := r.take(32)
146		r.done()
147		mutateReveal(cur, who, id, rev, ep, key, op)
148	case "Delete":
149		r.done()
150		mutateDelete(cur, who, id, rev, op)
151	case "AddWriter", "RemoveWriter", "ProposeOwner":
152		to := address(string(r.blob(90)))
153		r.done()
154		if action == "AddWriter" {
155			mutateAddWriter(cur, who, id, rev, to, op)
156		} else if action == "RemoveWriter" {
157			mutateRemoveWriter(cur, who, id, rev, to, op)
158		} else {
159			mutateProposeOwner(cur, who, id, rev, to, op)
160		}
161	case "SetPolicy":
162		kind := r.byte()
163		key := string(r.blob(233))
164		r.done()
165		mutateSetPolicy(cur, who, id, rev, kind, key, op)
166	case "SetPublicWrites":
167		on := r.boolean()
168		r.done()
169		mutateSetPublicWrites(cur, who, id, rev, on, op)
170	case "SetCommentMode":
171		on := r.boolean()
172		r.done()
173		mutateSetCommentMode(cur, who, id, rev, on, op)
174	case "SetListed":
175		on := r.boolean()
176		r.done()
177		mutateSetListed(cur, who, id, rev, on, op)
178	case "ModerateListing":
179		on := r.boolean()
180		r.done()
181		mutateModerateListing(cur, who, id, rev, on, op)
182	case "SetGovWriters":
183		on := r.boolean()
184		r.done()
185		mutateSetGovWriters(cur, who, id, rev, on, op)
186	case "HideComment", "ResolveComment":
187		cid := r.id()
188		commentRev := r.u64()
189		on := r.boolean()
190		r.done()
191		if action == "HideComment" {
192			mutateHideComment(cur, who, id, cid, commentRev, on, op)
193		} else {
194			mutateResolveComment(cur, who, id, cid, commentRev, on, op)
195		}
196	case "RejectAccessRequest":
197		to := address(string(r.blob(90)))
198		priorOp := r.id()
199		r.done()
200		v, ok := requests.Get(requestKey(id, to))
201		if !ok || v.(*accessRequest).Last.Operation != priorOp {
202			panic("notes: stale request approval")
203		}
204		mutateRejectAccessRequest(cur, who, id, to, op)
205	case "Purge":
206		limit := r.byte()
207		r.done()
208		mutatePurge(cur, who, id, int(limit))
209	default:
210		panic("notes: unknown governance action")
211	}
212}