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

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}