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}