Transaction

1294686A2E8971…FE3331A7DBD0

Block 806,211 · index 0 · indexed

Summary

Hash
1294686A2E8971729992E893D3FBDFF3AEB22E6E1FC4CDFA1A01FE3331A7DBD0
Block
806,211
Size
98787 bytes
Gas used
130,947,581 / 200,000,000
Fee
300000 ugnot
Status
success

Messages

#1AddPackagegno.land/r/samcrew/memba_notes_v124 arguments
Attached funds
25000000 ugnot

Arguments · 24

  1. #1memba_notes_v1
  2. #2acl.gno
  3. #3package memba_notes_v1 import ( keys "gno.land/r/samcrew/enckeys_v1" "strings" ) func writer(n *note, who address) bool { for _, a := range n.Writers { if a == who { return true } } return false } func canWrite(cur realm, n *note, who address) bool { if who == n.Owner { return true } if n.Mode == ModePublicOpen { return false } if writer(n, who) { return true } if encrypted(n.Mode) && !contains(n.Readers, who) { return false } if n.GovWriters && n.Owner == govAddress && govMember(who) { return true } return policyAllows(cur, n, who, OpWrite) } func requireWrite(cur realm, n *note, who address) { if !canWrite(cur, n, who) { panic("notes: writer required") } } func AddWriter(cur realm, id [16]byte, baseRevision uint64, to address, opID [16]byte) { who := enter(cur, false) defer leave() mutateAddWriter(cur, who, id, baseRevision, to, opID) } func mutateAddWriter(cur realm, who address, id [16]byte, baseRevision uint64, to address, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if !public(n.Mode) { panic("notes: encrypted ACL requires atomic manifest") } to = keys.CanonicalAddress(to) if to == n.Owner || writer(n, to) { panic("notes: writer already present") } if len(n.Writers) >= MaxReaders { panic("notes: too many writers") } operation(n, opID) n.Writers = append(n.Writers, to) sharedIndex.Set(string(to)+":"+indexKey(n), idText(id)) bump(n, who, opID, "writer-add") } func RemoveWriter(cur realm, id [16]byte, baseRevision uint64, to address, opID [16]byte) { who := enter(cur, false) defer leave() mutateRemoveWriter(cur, who, id, baseRevision, to, opID) } func mutateRemoveWriter(cur realm, who address, id [16]byte, baseRevision uint64, to address, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if !public(n.Mode) { panic("notes: encrypted ACL requires rotation") } to = keys.CanonicalAddress(to) if !writer(n, to) { panic("notes: writer missing") } operation(n, opID) for i, a := range n.Writers { if a == to { n.Writers = append(n.Writers[:i], n.Writers[i+1:]...) break } } sharedIndex.Remove(string(to) + ":" + indexKey(n)) bump(n, who, opID, "writer-remove") } func ProposeOwner(cur realm, id [16]byte, baseRevision uint64, to address, opID [16]byte) { who := enter(cur, false) defer leave() mutateProposeOwner(cur, who, id, baseRevision, to, opID) } func mutateProposeOwner(cur realm, who address, id [16]byte, baseRevision uint64, to address, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if to != "" { to = keys.CanonicalAddress(to) } if to == n.Owner { panic("notes: already owner") } operation(n, opID) n.PendingOwner = to bump(n, who, opID, "owner-propose") } func AcceptOwnership(cur realm, id [16]byte, baseRevision uint64, opID [16]byte) { who := enter(cur, false) defer leave() mutateAcceptOwnership(cur, who, id, baseRevision, opID) } func mutateAcceptOwnership(cur realm, who address, id [16]byte, baseRevision uint64, opID [16]byte) { n := live(id) expected(n, baseRevision) if n.PendingOwner == "" || who != n.PendingOwner { panic("notes: pending owner required") } isRealm := strings.HasPrefix(cur.Previous().PkgPath(), "gno.land/r/") || who == govAddress if encrypted(n.Mode) && (!isRealm && !contains(n.Readers, who) || isRealm && n.Mode == ModePrivate) { panic("notes: encrypted ownership requires current wrap/shared mode") } if n.OwnerGeneration == ^uint64(0) { panic("notes: owner generation exhausted") } operation(n, opID) unindex(n) n.OwnerIsRealm = isRealm resetPublicWrites(n) n.Owner = who n.PendingOwner = "" n.OwnerGeneration++ n.GovWriters = false if encrypted(n.Mode) { preserveTransferredWriters(n) } index(n) bump(n, who, opID, "owner-accept") } func SetListed(cur realm, id [16]byte, baseRevision uint64, listed bool, opID [16]byte) { who := enter(cur, false) defer leave() mutateSetListed(cur, who, id, baseRevision, listed, opID) } func mutateSetListed(cur realm, who address, id [16]byte, baseRevision uint64, listed bool, opID [16]byte) { n := live(id) if who != n.Owner && moderator(who) { mutateModerateListing(cur, who, id, baseRevision, listed, opID) return } owner(n, who) expected(n, baseRevision) if !public(n.Mode) { panic("notes: public note required") } operation(n, opID) unindex(n) n.Listed = listed index(n) bump(n, who, opID, "listing") } func WritersJSON(idHex string) string { n := find(readID(idHex)) out := "[" for i, a := range n.Writers { if i > 0 { out += "," } out += q(string(a)) } return out + "]" } func ListSharedWithJSON(addr address, cursor string, limit int) string { return page(sharedIndex, string(keys.CanonicalAddress(addr))+":", cursor, limit) }
  4. #4admin.gno
  5. #5package memba_notes_v1 import ( "chain" keys "gno.land/r/samcrew/enckeys_v1" ) func administrator(who address) { if who != admin { panic("notes: admin required") } } func SetCreateFee(cur realm, fee int64) { who := enter(cur, false) defer leave() administrator(who) if fee < 0 || fee > 1000000 { panic("notes: fee out of range") } createFee = fee } func SetTreasury(cur realm, to address) { who := enter(cur, false) defer leave() administrator(who) to = keys.CanonicalAddress(to) if to == cur.Address() || to == chain.DeriveStorageDepositAddr(RealmPath) { panic("notes: locked treasury") } treasury = to } func Pause(cur realm, on bool) { who := enter(cur, false) defer leave() administrator(who) paused = on } func TransferAdmin(cur realm, to address) { who := enter(cur, false) defer leave() administrator(who) to = keys.CanonicalAddress(to) if to == govAddress { panic("notes: governance cannot accept realm administration") } pendingAdmin = to } func AcceptAdmin(cur realm) { who := enter(cur, false) defer leave() if who != pendingAdmin || pendingAdmin == "" { panic("notes: pending admin required") } admin = who pendingAdmin = "" } // GetAdmin supplies the closed authority readback used by deployment rehearsal. func GetAdmin() address { return admin }
  6. #6backup.gno
  7. #7package memba_notes_v1 import ( "bytes" "chain/runtime" "crypto/bech32" "crypto/sha256" "encoding/binary" "encoding/hex" "gno.land/p/samcrew/avl" keys "gno.land/r/samcrew/enckeys_v1" "strconv" "strings" ) const MaxBackupBytes = 365 type backupRecord struct { Record []byte Mode uint8 Generation, Revision uint64 PublicKeyHash []byte Last receipt } var backupLatest = avl.NewTree() var backups = avl.NewTree() // A wallet-protected historical backup cannot become Vault-safe by relabelling. var standardBackupKeys = avl.NewTree() func backupKey(who address, generation uint64) string { s := strconv.FormatUint(generation, 10) return string(who) + ":" + strings.Repeat("0", 20-len(s)) + s } func backupText(record []byte, pos, cap int) (string, int) { if pos+4 > len(record) { panic("notes: backup truncated") } size := int(binary.BigEndian.Uint32(record[pos : pos+4])) pos += 4 if size < 1 || size > cap || pos+size > len(record) { panic("notes: backup text length") } for _, b := range record[pos : pos+size] { if b < 33 || b > 126 { panic("notes: backup ascii") } } return string(record[pos : pos+size]), pos + size } func validateBackup(who address, record []byte) (uint8, uint64, []byte) { if len(record) < 185 || len(record) > MaxBackupBytes || string(record[:4]) != "MNKB" || record[4] != 1 || record[5] != 1 { panic("notes: backup format") } mode := record[6] kdf := record[7] memory := binary.BigEndian.Uint32(record[8:12]) passes := binary.BigEndian.Uint32(record[12:16]) lanes := record[16] if !(mode == 1 && kdf == 1 && memory == 0 && passes == 0 && lanes == 0 || mode == 2 && kdf == 2 && memory == 65536 && passes == 3 && lanes == 1) { panic("notes: backup kdf profile") } chain, pos := backupText(record, 33, 64) realmPath, pos := backupText(record, pos, 128) if chain != runtime.ChainID() || realmPath != RealmPath || len(record) != pos+132 { panic("notes: backup network/length") } encoded, err := bech32.EncodeFromBase256("g", record[pos:pos+20]) if err != nil || address(encoded) != who { panic("notes: backup address") } pos += 20 generation := binary.BigEndian.Uint64(record[pos : pos+8]) pos += 8 hash := record[pos : pos+32] pk, gen, active := keys.GetKey(who, keys.SuiteXWingV1) h := sha256.Sum256(pk) if generation == 0 || generation != gen || !active || !bytes.Equal(hash, h[:]) { panic("notes: backup registry identity") } return mode, generation, copied(hash) } func SetSeedBackup(cur realm, baseRevision uint64, record []byte, opID [16]byte) { who := enter(cur, false) defer leave() if !cur.Previous().IsUserCall() { panic("notes: backup directly from account") } if opID == ([16]byte{}) || baseRevision == ^uint64(0) { panic("notes: invalid backup operation") } var revision uint64 if v, ok := backupLatest.Get(string(who)); ok { r := v.(*backupRecord) revision = r.Revision if r.Last.Operation == opID { panic("notes: operation id reused") } } if revision != baseRevision { panic("notes: stale backup revision") } mode, generation, hash := validateBackup(who, record) exposureKey := string(who) + ":" + hex.EncodeToString(hash) if mode == 2 { if _, seen := standardBackupKeys.Get(exposureKey); seen { panic("notes: vault requires independent identity") } } else { standardBackupKeys.Set(exposureKey, true) } r := &backupRecord{copied(record), mode, generation, revision + 1, hash, receipt{opID, who, runtime.ChainHeight()}} backups.Set(backupKey(who, generation), r) backupLatest.Set(string(who), r) } func backupJSON(r *backupRecord, withRecord bool) string { s := `{"mode":` + strconv.Itoa(int(r.Mode)) + `,"suite":"xwing-v1","generation":` + u(r.Generation) + `,"revision":` + u(r.Revision) + `,"public_key_hash":` + blob(r.PublicKeyHash) + `,"op_id":` + q(idText(r.Last.Operation)) + `,"actor":` + q(string(r.Last.Actor)) + `,"height":` + q(strconv.FormatInt(r.Last.Height, 10)) if withRecord { s += `,"record":` + blob(r.Record) } return s + `}` } func SeedBackupJSON(who address) string { v, ok := backupLatest.Get(string(keys.CanonicalAddress(who))) if !ok { return "null" } return backupJSON(v.(*backupRecord), true) } func SeedBackupAtJSON(who address, generation uint64) string { v, ok := backups.Get(backupKey(keys.CanonicalAddress(who), generation)) if !ok { return "null" } return backupJSON(v.(*backupRecord), true) } func SeedBackupsJSON(who address, cursor uint64, limit int) string { who = keys.CanonicalAddress(who) if limit < 1 || limit > MaxPage { panic("notes: page limit") } items := []string{} var next uint64 backups.Iterate(backupKey(who, cursor), string(who)+":\xff", func(k string, v interface{}) bool { r := v.(*backupRecord) if r.Generation <= cursor { return false } items = append(items, backupJSON(r, false)) next = r.Generation return len(items) == limit }) if len(items) < limit { next = 0 } return `{"items":[` + strings.Join(items, ",") + `],"next_cursor":` + u(next) + `}` }
  8. #8capabilities.gno
  9. #9package memba_notes_v1 import ( "gno.land/p/samcrew/avl" "strconv" ) // Only explicit public grants are stored. Private notes keep their existing ACL. var publicWriteGrants = avl.NewTree() func publicWritesEnabled(n *note) bool { _, granted := publicWriteGrants.Get(idText(n.ID)) return !n.Deleted && n.Mode == ModePublicOpen && granted } func resetPublicWrites(n *note) { if _, granted := publicWriteGrants.Get(idText(n.ID)); granted { publicWriteGrants.Remove(idText(n.ID)) } } // Community editing never grants ResolveComment or any owner/moderator action. // Keep requireWrite unchanged for those existing permissions. // While paused the grant is ignored, so only the existing ACL can edit. func requireContentWrite(cur realm, n *note, who address) { if !paused && publicWritesEnabled(n) && cur.Previous().IsUserCall() { return } requireWrite(cur, n, who) } func SetPublicWrites(cur realm, id [16]byte, baseRevision uint64, on bool, opID [16]byte) { who := enter(cur, false) defer leave() mutateSetPublicWrites(cur, who, id, baseRevision, on, opID) } func mutateSetPublicWrites(cur realm, who address, id [16]byte, baseRevision uint64, on bool, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if n.Mode != ModePublicOpen { panic("notes: public-open note required") } operation(n, opID) if publicWritesEnabled(n) == on { panic("notes: public writes unchanged") } if on { publicWriteGrants.Set(idText(id), true) } else { resetPublicWrites(n) } bump(n, who, opID, "public-writes") } // Separate schema: existing NoteJSON consumers retain their exact field set. func PublicCapabilitiesJSON(idHex string) string { v, ok := notes.Get(idText(readID(idHex))) if !ok { return "null" } n := v.(*note) if !public(n.Mode) { return "null" } return `{"schema":"memba-notes/public-capabilities/v1","id":` + q(idText(n.ID)) + `,"state_revision":` + u(n.Revision) + `,"owner_generation":` + u(n.OwnerGeneration) + `,"mode":` + strconv.Itoa(int(n.Mode)) + `,"deleted":` + strconv.FormatBool(n.Deleted) + `,"allow_public_writes":` + strconv.FormatBool(publicWritesEnabled(n)) + `}` }
  10. #10chunks.gno
  11. #11package memba_notes_v1 import "strconv" const MaxBodyChunkBytes = 8192 func NoteMetaJSON(idHex string) string { v, ok := notes.Get(idText(readID(idHex))) if !ok { return "null" } return noteJSON(v.(*note), false) } // Each page is tied to the complete state revision, including access/mode and // epoch changes. Clients concatenate binary chunks before decoding UTF8/AEAD. func NoteBodyChunkJSON(idHex string, expectedRevision uint64, offset, limit int) string { n := find(readID(idHex)) if expectedRevision != n.Revision { panic("notes: stale body revision") } if limit < 1 || limit > MaxBodyChunkBytes || offset < 0 || offset > n.BodyBytes { panic("notes: body chunk bounds") } end := offset + limit if end > n.BodyBytes { end = n.BodyBytes } return `{"id":` + q(idText(n.ID)) + `,"state_revision":` + u(n.Revision) + `,"body_revision":` + u(n.BodyRevision) + `,"epoch":` + u(uint64(n.Epoch)) + `,"offset":` + q(strconv.Itoa(offset)) + `,"total":` + q(strconv.Itoa(n.BodyBytes)) + `,"next_offset":` + q(strconv.Itoa(end)) + `,"chunk_blob":` + blob(n.Body[offset:end]) + `}` }
  12. #12comments.gno
  13. #13package memba_notes_v1 import ( "chain/runtime" "gno.land/p/samcrew/avl" "strconv" "strings" "unicode/utf8" ) const MaxCommentBytes = 4000 const MaxAnchorBytes = 600 const CommentCooldownBlocks int64 = 10 const MaxCommentPage = 5 type comment struct { Sequence uint64 ID, Parent [16]byte Author address BodyRevision uint64 Epoch uint32 Anchor, Body []byte Revision uint64 CreatedHeight int64 Last receipt Deleted, Hidden, Resolved bool Encrypted bool } var comments = avl.NewTree() var commentIndex = avl.NewTree() var cooldowns = avl.NewTree() func commentKey(id, cid [16]byte) string { return idText(id) + ":" + idText(cid) } func getComment(id, cid [16]byte) *comment { v, ok := comments.Get(commentKey(id, cid)) if !ok { panic("notes: comment missing") } return v.(*comment) } func commentExpected(c *comment, rev uint64, op [16]byte) { if c.Revision != rev || rev == ^uint64(0) { panic("notes: stale comment revision") } if op == ([16]byte{}) || c.Last.Operation == op { panic("notes: invalid comment operation") } } func commentReceipt(c *comment, who address, op [16]byte) { c.Revision++ c.Last = receipt{op, who, runtime.ChainHeight()} } func mayComment(cur realm, n *note, who address) bool { if n.Mode == ModePublicOpen { return true } if n.Mode == ModePublic { return who == n.Owner || writer(n, who) || n.GovWriters && n.Owner == govAddress && govMember(who) || policyAllows(cur, n, who, OpComment) } if n.Mode == ModeShared { return contains(n.Readers, who) } return false } // Public comments carry UTF8 anchor/body separately. Encrypted comments will // carry their combined authenticated payload in Body and an empty Anchor. func AddComment(cur realm, id, cid [16]byte, bodyRevision uint64, epoch uint32, parent [16]byte, anchor, body []byte, opID [16]byte) { who := enter(cur, false) defer leave() n := live(id) if paused { panic("notes: comments paused") } if !mayComment(cur, n, who) { panic("notes: comments denied") } if cid == ([16]byte{}) || opID == ([16]byte{}) { panic("notes: empty comment id") } if _, ok := comments.Get(commentKey(id, cid)); ok { panic("notes: comment id used") } if bodyRevision == 0 || bodyRevision > n.BodyRevision || epoch != n.Epoch { panic("notes: bad comment anchor version") } if public(n.Mode) && (len(anchor) > MaxAnchorBytes || !utf8.Valid(anchor) || len(body) == 0 || len(body) > MaxCommentBytes || !utf8.Valid(body) || utf8.RuneCount(body) > 1000) { panic("notes: invalid comment payload") } privateWritable(n) if encrypted(n.Mode) { if len(anchor) != 0 { panic("notes: encrypted anchor must be inside payload") } validateCipher(body, n.Epoch, 3, 1, MaxCipherCommentBytes) } if parent != ([16]byte{}) { p := getComment(id, parent) if p.Deleted { panic("notes: parent deleted") } } ck := idText(id) + ":" + string(who) h := runtime.ChainHeight() if v, ok := cooldowns.Get(ck); ok && h-v.(int64) < CommentCooldownBlocks { panic("notes: comment cooldown") } if n.CommentSequence == ^uint64(0) { panic("notes: comment sequence exhausted") } n.CommentSequence++ c := &comment{Sequence: n.CommentSequence, Encrypted: encrypted(n.Mode), ID: cid, Parent: parent, Author: who, BodyRevision: bodyRevision, Epoch: epoch, Anchor: copied(anchor), Body: copied(body), CreatedHeight: h} commentReceipt(c, who, opID) comments.Set(commentKey(id, cid), c) seq := strconv.FormatUint(c.Sequence, 10) commentIndex.Set(idText(id)+":"+strings.Repeat("0", 20-len(seq))+seq+":"+idText(cid), commentKey(id, cid)) cooldowns.Set(ck, h) historyCommentChange(n, c, "comment-add") } func DeleteComment(cur realm, id, cid [16]byte, baseRevision uint64, opID [16]byte) { who := enter(cur, false) defer leave() // Authors can clear their current payload even after the note is deleted or // paused. Public history keeps the original unless a moderator redacts it. n := find(id) c := getComment(id, cid) if c.Author != who { panic("notes: comment author required") } if c.Deleted { panic("notes: comment deleted") } commentExpected(c, baseRevision, opID) c.Deleted = true c.Anchor = nil c.Body = nil commentReceipt(c, who, opID) historyCommentChange(n, c, "comment-delete") } func ResolveComment(cur realm, id, cid [16]byte, baseRevision uint64, resolved bool, opID [16]byte) { who := enter(cur, false) defer leave() mutateResolveComment(cur, who, id, cid, baseRevision, resolved, opID) } func mutateResolveComment(cur realm, who address, id, cid [16]byte, baseRevision uint64, resolved bool, opID [16]byte) { n := live(id) requireWrite(cur, n, who) c := getComment(id, cid) commentExpected(c, baseRevision, opID) c.Resolved = resolved commentReceipt(c, who, opID) historyCommentChange(n, c, "comment-resolution") } func HideComment(cur realm, id, cid [16]byte, baseRevision uint64, hidden bool, opID [16]byte) { who := enter(cur, false) defer leave() mutateHideComment(cur, who, id, cid, baseRevision, hidden, opID) } func mutateHideComment(cur realm, who address, id, cid [16]byte, baseRevision uint64, hidden bool, opID [16]byte) { n := live(id) if who != n.Owner && !moderator(who) { panic("notes: owner required or moderator") } c := getComment(id, cid) commentExpected(c, baseRevision, opID) if v, ok := historyComments.Get(commentKey(id, cid)); ok && !hidden && v.(*historyComment).Redacted { panic("notes: comment redacted") } c.Hidden = hidden commentReceipt(c, who, opID) historyCommentChange(n, c, "comment-visibility") } func commentJSON(c *comment) string { return `{"id":` + q(idText(c.ID)) + `,"parent":` + q(idText(c.Parent)) + `,"author":` + q(string(c.Author)) + `,"encrypted":` + strconv.FormatBool(c.Encrypted) + `,"body_revision":` + u(c.BodyRevision) + `,"epoch":` + u(uint64(c.Epoch)) + `,"revision":` + u(c.Revision) + `,"anchor_blob":` + blob(c.Anchor) + `,"body_blob":` + blob(c.Body) + `,"created_height":` + q(strconv.FormatInt(c.CreatedHeight, 10)) + `,"op_id":` + q(idText(c.Last.Operation)) + `,"actor":` + q(string(c.Last.Actor)) + `,"height":` + q(strconv.FormatInt(c.Last.Height, 10)) + `,"deleted":` + strconv.FormatBool(c.Deleted) + `,"hidden":` + strconv.FormatBool(c.Hidden) + `,"resolved":` + strconv.FormatBool(c.Resolved) + `}` } func CommentJSON(idHex, cidHex string) string { v, ok := comments.Get(commentKey(readID(idHex), readID(cidHex))) if !ok { return "null" } return commentJSON(v.(*comment)) } // Comments paginate in append order; editing or moderation never reorders them. func CommentsJSON(idHex, cursor string, limit int) string { id := readID(idHex) find(id) if limit < 1 || limit > MaxCommentPage { panic("notes: page limit") } if cursor != "" { if len(cursor) != 53 || cursor[20] != ':' { panic("notes: bad cursor") } readID(cursor[21:]) for _, c := range cursor[:20] { if c < '0' || c > '9' { panic("notes: bad cursor") } } } prefix := idText(id) + ":" start := prefix + cursor items := []string{} next := "" commentIndex.Iterate(start, prefix+"\xff", func(k string, v interface{}) bool { if cursor != "" && k == start { return false } cv, ok := comments.Get(v.(string)) if !ok { panic("notes: corrupt comment index") } items = append(items, commentJSON(cv.(*comment))) next = strings.TrimPrefix(k, prefix) return len(items) == limit }) if len(items) < limit { next = "" } return `{"items":[` + strings.Join(items, ",") + `],"next_cursor":` + q(next) + `}` } // Purge never operates on a live note. Each call erases at most limit records // (including cooldowns); a permanent note tombstone prevents ID reuse. func Purge(cur realm, id [16]byte, limit int) int { who := enter(cur, false) defer leave() return mutatePurge(cur, who, id, limit) } func mutatePurge(cur realm, who address, id [16]byte, limit int) int { n := find(id) owner(n, who) if !n.Deleted { panic("notes: delete before purge") } if limit < 1 || limit > MaxPage { panic("notes: purge limit") } counts := [5]int{} counts[0] = purgePrefix(commentIndex, idText(id)+":", limit) counts[1] = purgePrefix(comments, idText(id)+":", limit-counts[0]) counts[2] = purgePrefix(cooldowns, idText(id)+":", limit-counts[0]-counts[1]) counts[3] = purgePrefix(epochs, idText(id)+":", limit-counts[0]-counts[1]-counts[2]) counts[4] = purgeRequests(n, limit-counts[0]-counts[1]-counts[2]-counts[3]) historyCleanup(n, who, counts) return counts[0]+counts[1]+counts[2]+counts[3]+counts[4] } func purgePrefix(tree *avl.Tree, prefix string, limit int) int { if limit == 0 { return 0 } doomed := []string{} tree.Iterate(prefix, prefix+"\xff", func(k string, v interface{}) bool { doomed = append(doomed, k); return len(doomed) == limit }) for _, k := range doomed { tree.Remove(k) } return len(doomed) }
  14. #14crypto.gno
  15. #15package memba_notes_v1 import ( "bytes" "chain/runtime" "crypto/bech32" "crypto/sha256" "encoding/binary" keys "gno.land/r/samcrew/enckeys_v1" "strconv" ) const ManifestEntryBytes = 1299 const MaxManifestBytes = 2 + MaxReaders*ManifestEntryBytes const MaxCipherCommentBytes = 5687 type recipient struct { Who address Writer bool Generation uint64 } func encrypted(mode uint8) bool { return mode <= ModeLink } func contains(addrs []address, who address) bool { for _, a := range addrs { if a == who { return true } } return false } func parseManifest(data []byte, epoch uint32, owner address, mode uint8) []recipient { return parseManifestFor(data, epoch, owner, mode, "") } // A self refresh checks the new caller identity while preserving every other // already-stored envelope, including revoked or superseded identities. func parseManifestFor(data []byte, epoch uint32, owner address, mode uint8, currentOnly address) []recipient { if len(data) < 2 || len(data) > MaxManifestBytes || data[0] != 1 || data[1] < 1 || data[1] > MaxReaders || len(data) != 2+int(data[1])*ManifestEntryBytes { panic("notes: manifest header/length") } result := []recipient{} previous := []byte(nil) hasOwner := false hasWriter := false for i := 0; i < int(data[1]); i++ { off := 2 + i*ManifestEntryBytes raw := data[off : off+20] if i > 0 && bytes.Compare(raw, previous) <= 0 || data[off+20] > 1 { panic("notes: manifest order/role") } previous = raw encoded, err := bech32.EncodeFromBase256("g", raw) if err != nil { panic("notes: recipient address") } who := address(encoded) generation := binary.BigEndian.Uint64(data[off+21 : off+29]) if generation == 0 { panic("notes: zero identity generation") } wr := off + 61 if data[wr] != 1 || data[wr+1] != 1 || binary.BigEndian.Uint32(data[wr+2:wr+6]) != epoch { panic("notes: wrap suite/epoch") } if binary.BigEndian.Uint64(data[wr+6:wr+14]) != generation || !bytes.Equal(data[off+29:off+61], data[wr+14:wr+46]) { panic("notes: wrap identity mismatch") } if currentOnly == "" || currentOnly == who { pk, gen, active := keys.GetKey(who, keys.SuiteXWingV1) if !active || gen != generation { panic("notes: recipient key inactive/stale") } hash := sha256.Sum256(pk) if !bytes.Equal(hash[:], data[off+29:off+61]) { panic("notes: recipient public key hash") } } isWriter := data[off+20] == 1 if isWriter { hasWriter = true } if who == owner { if !isWriter { panic("notes: owner must write") } hasOwner = true } if mode == ModeLink && !isWriter { panic("notes: link wraps are for writers") } result = append(result, recipient{who, isWriter, generation}) } if owner != "" && (!hasOwner || !hasWriter) || mode == ModePrivate && (len(result) != 1 || owner == "") { panic("notes: missing owner/private readers") } return result } func validateCipher(blob []byte, epoch uint32, field uint8, revision uint64, cap int) { if len(blob) < 59 || len(blob) > cap || blob[0] != 1 || blob[1] != 1 || binary.BigEndian.Uint32(blob[2:6]) != epoch || blob[6] != field || binary.BigEndian.Uint64(blob[7:15]) != revision { panic("notes: invalid cipher envelope") } } func validateCommitment(kc []byte) { if len(kc) != 32 || bytes.Equal(kc, make([]byte, 32)) { panic("notes: invalid epoch commitment") } } func appendText(b []byte, s string) []byte { size := make([]byte, 4) binary.BigEndian.PutUint32(size, uint32(len(s))) b = append(b, size...) return append(b, []byte(s)...) } func epochCommitment(id [16]byte, epoch uint32, key []byte) []byte { b := appendText(nil, "memba-notes/v1/epoch-commitment") b = appendText(b, runtime.ChainID()) b = appendText(b, RealmPath) b = append(b, id[:]...) ep := make([]byte, 4) binary.BigEndian.PutUint32(ep, epoch) b = append(b, ep...) b = append(b, key...) h := sha256.Sum256(b) return h[:] } func manifestAddress(raw []byte) address { s, err := bech32.EncodeFromBase256("g", raw) if err != nil { panic("notes: manifest address") } return address(s) } func canonicalRecipient(who address) address { return keys.CanonicalAddress(who) } func wrapJSON(entry []byte, epoch uint32, who address) string { return `{"address":` + q(string(who)) + `,"epoch":` + u(uint64(epoch)) + `,"writer":` + strconv.FormatBool(entry[20] == 1) + `,"generation":` + u(binary.BigEndian.Uint64(entry[21:29])) + `,"public_key_hash":` + blob(entry[29:61]) + `,"wrap":` + blob(entry[61:]) + `}` }
  16. #16epochs.gno
  17. #17package memba_notes_v1 import ( "bytes" "gno.land/p/samcrew/avl" "strconv" "strings" ) type epochRecord struct { Epoch uint32 Manifest, Commitment, RevealedKey []byte } var epochs = avl.NewTree() func epochKey(id [16]byte, epoch uint32) string { s := strconv.FormatUint(uint64(epoch), 10) return idText(id) + ":" + strings.Repeat("0", 10-len(s)) + s } func getEpoch(id [16]byte, epoch uint32) *epochRecord { v, ok := epochs.Get(epochKey(id, epoch)) if !ok { panic("notes: epoch missing") } return v.(*epochRecord) } func privateWritable(n *note) { if encrypted(n.Mode) && len(getEpoch(n.ID, n.Epoch).RevealedKey) != 0 { panic("notes: revealed epoch requires rotation") } } func setRecipients(n *note, list []recipient) { n.Readers = nil n.Writers = nil for _, r := range list { n.Readers = append(n.Readers, r.Who) if r.Writer && r.Who != n.Owner { n.Writers = append(n.Writers, r.Who) } } } func initializeEncrypted(n *note, access, kc []byte) { validateCommitment(kc) list := parseManifest(access, 1, n.Owner, n.Mode) validateCipher(n.Title, 1, 1, 1, MaxCipherTitleBytes) validateCipher(n.Body, 1, 2, 1, MaxCipherBodyBytes) n.Epoch = 1 n.Commitment = copied(kc) setRecipients(n, list) epochs.Set(epochKey(n.ID, 1), &epochRecord{Epoch: 1, Manifest: copied(access), Commitment: copied(kc)}) } // Rotate installs the entire next epoch in one transaction. Historical epoch // records stay available until Delete+boundedPurge; rotation never scans them. func Rotate(cur realm, id [16]byte, baseRevision uint64, expectedEpoch uint32, mode uint8, title, body, manifest, kc []byte, opID [16]byte) { who := enter(cur, false) defer leave() mutateRotate(cur, who, id, baseRevision, expectedEpoch, mode, title, body, manifest, kc, opID) } func mutateRotate(cur realm, who address, id [16]byte, baseRevision uint64, expectedEpoch uint32, mode uint8, title, body, manifest, kc []byte, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if !encrypted(n.Mode) || !encrypted(mode) { panic("notes: encrypted rotation required") } if expectedEpoch != n.Epoch || n.Epoch == ^uint32(0) { panic("notes: stale/exhausted epoch") } if n.TitleRevision == ^uint64(0) || n.BodyRevision == ^uint64(0) { panic("notes: field revision exhausted") } ep := n.Epoch + 1 validateCommitment(kc) if bytes.Equal(kc, n.Commitment) { panic("notes: fresh epoch commitment required") } list := parseManifest(manifest, ep, manifestOwner(n), mode) validateCipher(title, ep, 1, n.TitleRevision+1, MaxCipherTitleBytes) validateCipher(body, ep, 2, n.BodyRevision+1, MaxCipherBodyBytes) operation(n, opID) unindex(n) n.Mode = mode n.Epoch = ep n.Title = copied(title) n.Body = copied(body) n.BodyBytes = len(body) n.TitleRevision++ n.BodyRevision++ n.Commitment = copied(kc) n.PendingOwner = "" setRecipients(n, list) index(n) epochs.Set(epochKey(id, ep), &epochRecord{Epoch: ep, Manifest: copied(manifest), Commitment: copied(kc)}) acknowledgeRequests(n, who, opID) bump(n, who, opID, "rotate") } // ApplyAccess grants access to the CURRENT epoch and its history. Removing a // reader/writer or changing link scope requires Rotate, never this entrypoint. func ApplyAccess(cur realm, id [16]byte, baseRevision uint64, expectedEpoch uint32, mode uint8, manifest []byte, opID [16]byte) { who := enter(cur, false) defer leave() mutateApplyAccess(cur, who, id, baseRevision, expectedEpoch, mode, manifest, opID) } func mutateApplyAccess(cur realm, who address, id [16]byte, baseRevision uint64, expectedEpoch uint32, mode uint8, manifest []byte, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) privateWritable(n) if !encrypted(n.Mode) || expectedEpoch != n.Epoch || !(mode == n.Mode || n.Mode == ModePrivate && mode == ModeShared) { panic("notes: access transition requires rotation") } list := parseManifest(manifest, n.Epoch, manifestOwner(n), mode) for _, a := range n.Readers { found := false for _, r := range list { if r.Who == a { found = true if writer(n, a) && !r.Writer { panic("notes: writer removal requires rotation") } } } if !found { panic("notes: reader removal requires rotation") } } operation(n, opID) unindex(n) n.Mode = mode setRecipients(n, list) index(n) getEpoch(id, n.Epoch).Manifest = copied(manifest) acknowledgeRequests(n, who, opID) bump(n, who, opID, "access") } func Publish(cur realm, id [16]byte, baseRevision uint64, mode uint8, title, body []byte, opID [16]byte) { who := enter(cur, false) defer leave() mutatePublish(cur, who, id, baseRevision, mode, title, body, opID) } func mutatePublish(cur realm, who address, id [16]byte, baseRevision uint64, mode uint8, title, body []byte, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if !encrypted(n.Mode) || !public(mode) { panic("notes: publish transition") } if n.TitleRevision == ^uint64(0) || n.BodyRevision == ^uint64(0) { panic("notes: field revision exhausted") } validatePublic(title, body) operation(n, opID) unindex(n) n.Mode = mode n.Title = copied(title) n.Body = copied(body) n.BodyBytes = len(body) n.TitleRevision++ n.BodyRevision++ n.Listed = true n.Readers = nil n.Commitment = nil n.PendingOwner = "" index(n) bump(n, who, opID, "publish") } func Reveal(cur realm, id [16]byte, baseRevision uint64, epoch uint32, key []byte, opID [16]byte) { who := enter(cur, false) defer leave() mutateReveal(cur, who, id, baseRevision, epoch, key, opID) } func mutateReveal(cur realm, who address, id [16]byte, baseRevision uint64, epoch uint32, key []byte, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) ep := getEpoch(id, epoch) if len(key) != 32 || len(ep.RevealedKey) != 0 || !bytes.Equal(epochCommitment(id, epoch, key), ep.Commitment) { panic("notes: invalid/already revealed key") } operation(n, opID) ep.RevealedKey = copied(key) bumpHistory(n, who, opID, "reveal", `,"target_epoch":` + u(uint64(epoch))) } func epochJSON(ep *epochRecord, withManifest bool) string { out := `{"epoch":` + u(uint64(ep.Epoch)) + `,"commitment":` + blob(ep.Commitment) + `,"revealed_key":` + blob(ep.RevealedKey) if withManifest { out += `,"manifest":` + blob(ep.Manifest) } return out + `}` } func EpochJSON(idHex string, epoch uint32) string { id := readID(idHex) v, ok := epochs.Get(epochKey(id, epoch)) if !ok { return "null" } return epochJSON(v.(*epochRecord), true) } func EpochsJSON(idHex string, cursor uint32, limit int) string { id := readID(idHex) find(id) if limit < 1 || limit > MaxPage { panic("notes: page limit") } items := []string{} var next uint32 epochs.Iterate(epochKey(id, cursor), idText(id)+":\xff", func(k string, v interface{}) bool { ep := v.(*epochRecord) if ep.Epoch <= cursor { return false } items = append(items, epochJSON(ep, false)) next = ep.Epoch return len(items) == limit }) if len(items) < limit { next = 0 } return `{"items":[` + strings.Join(items, ",") + `],"next_cursor":` + u(uint64(next)) + `}` } func ReadersJSON(idHex string) string { n := find(readID(idHex)) out := "[" for i, a := range n.Readers { if i > 0 { out += "," } out += q(string(a)) } return out + "]" } func manifestOwner(n *note) address { if n.OwnerIsRealm { return "" } return n.Owner } // Ownership changes do not silently revoke an existing manifest writer. // Revocation requires the new owner to rotate; old key knowledge also remains. func preserveTransferredWriters(n *note) { data := getEpoch(n.ID, n.Epoch).Manifest n.Writers = nil for i := 0; i < int(data[1]); i++ { off := 2 + i*ManifestEntryBytes if data[off+20] == 1 { who := manifestAddress(data[off : off+20]) if who != n.Owner { n.Writers = append(n.Writers, who) } } } } func WrapJSON(idHex string, epoch uint32, who address) string { id := readID(idHex) who = canonicalRecipient(who) v, ok := epochs.Get(epochKey(id, epoch)) if !ok { return "null" } data := v.(*epochRecord).Manifest for i := 0; i < int(data[1]); i++ { off := 2 + i*ManifestEntryBytes if manifestAddress(data[off:off+20]) == who { return wrapJSON(data[off:off+ManifestEntryBytes], epoch, who) } } return "null" } // RefreshOwnWrap changes only the caller's identity envelope. It cannot change // roles, readers, modes or epochs, and never rewrites anybody else's recovery. func RefreshOwnWrap(cur realm, id [16]byte, baseRevision uint64, expectedEpoch uint32, manifest []byte, opID [16]byte) { who := enter(cur, false) defer leave() n := live(id) expected(n, baseRevision) privateWritable(n) if !cur.Previous().IsUserCall() || !encrypted(n.Mode) || expectedEpoch != n.Epoch || !contains(n.Readers, who) { panic("notes: current reader required") } // A reader who accepted Shared ownership may retain a false explicit writer // flag: ownership is implicit authority. Self-refresh preserves that flag. manifestAccountOwner := address("") if n.Mode == ModePrivate { manifestAccountOwner = n.Owner } parseManifestFor(manifest, n.Epoch, manifestAccountOwner, n.Mode, who) old := getEpoch(id, n.Epoch).Manifest if len(old) != len(manifest) { panic("notes: self refresh cannot change readers") } changed := false for i := 0; i < int(old[1]); i++ { off := 2 + i*ManifestEntryBytes previous := old[off : off+ManifestEntryBytes] next := manifest[off : off+ManifestEntryBytes] if manifestAddress(previous[:20]) == who { if string(previous[:21]) != string(next[:21]) { panic("notes: self refresh cannot change role") } changed = string(previous) != string(next) } else if string(previous) != string(next) { panic("notes: self refresh cannot change another wrap") } } if !changed { panic("notes: wrap unchanged") } operation(n, opID) getEpoch(id, n.Epoch).Manifest = copied(manifest) bump(n, who, opID, "self-rekey") }
  18. #18getters.gno
  19. #19package memba_notes_v1 import ( "encoding/base64" "gno.land/p/samcrew/avl" keys "gno.land/r/samcrew/enckeys_v1" "strconv" "strings" ) func q(s string) string { return strconv.Quote(s) } func u(n uint64) string { return q(strconv.FormatUint(n, 10)) } func blob(b []byte) string { return "\"" + base64.StdEncoding.EncodeToString(b) + "\"" } func noteJSON(n *note, withBody bool) string { s := `{"id":` + q(idText(n.ID)) + `,"owner":` + q(string(n.Owner)) + `,"pending_owner":` + q(string(n.PendingOwner)) + `,"owner_generation":` + u(n.OwnerGeneration) + `,"mode":` + strconv.Itoa(int(n.Mode)) + `,"state_revision":` + u(n.Revision) + `,"title_revision":` + u(n.TitleRevision) + `,"body_revision":` + u(n.BodyRevision) + `,"epoch":` + u(uint64(n.Epoch)) + `,"body_bytes":` + q(strconv.Itoa(n.BodyBytes)) + `,"title_blob":` + blob(n.Title) + `,"commitment":` + blob(n.Commitment) + `,"deleted":` + strconv.FormatBool(n.Deleted) + `,"listed":` + strconv.FormatBool(n.Listed && !n.ModeratorHidden) + `,"owner_listed":` + strconv.FormatBool(n.Listed) + `,"moderator_hidden":` + strconv.FormatBool(n.ModeratorHidden) + `,"created_height":` + q(strconv.FormatInt(n.CreatedHeight, 10)) + `,"op_id":` + q(idText(n.Last.Operation)) + `,"executor":` + q(string(n.LastExecutor)) + `,"gov_writers":` + strconv.FormatBool(n.GovWriters) + `,"actor":` + q(string(n.Last.Actor)) + `,"height":` + q(strconv.FormatInt(n.Last.Height, 10)) if withBody { s += `,"body_blob":` + blob(n.Body) } return s + `}` } func NoteJSON(idHex string) string { v, ok := notes.Get(idText(readID(idHex))) if !ok { return "null" } return noteJSON(v.(*note), true) } func page(tree *avl.Tree, prefix, cursor string, limit int) string { if limit < 1 || limit > MaxPage { panic("notes: page limit") } if len(cursor) > 53 { panic("notes: cursor length") } if cursor != "" { if len(cursor) != 53 || cursor[20] != ':' { panic("notes: bad cursor") } readID(cursor[21:]) for _, c := range cursor[:20] { if c < '0' || c > '9' { panic("notes: bad cursor") } } } end := prefix + "\xff" if cursor != "" { end = prefix + cursor } items := []string{} next := "" tree.ReverseIterate(prefix, end, func(key string, value interface{}) bool { if cursor != "" && key == end { return false } v, ok := notes.Get(value.(string)) if !ok { panic("notes: corrupt index") } items = append(items, noteJSON(v.(*note), false)) next = strings.TrimPrefix(key, prefix) return len(items) == limit }) if len(items) < limit { next = "" } return `{"items":[` + strings.Join(items, ",") + `],"next_cursor":` + q(next) + `}` } func ListByOwnerJSON(addr address, cursor string, limit int) string { return page(ownerIndex, string(keys.CanonicalAddress(addr))+":", cursor, limit) } func ListPublicJSON(cursor string, limit int) string { return page(publicIndex, "", cursor, limit) } func ConfigJSON() string { return `{"realm":` + q(RealmPath) + `,"admin":` + q(string(admin)) + `,"pending_admin":` + q(string(pendingAdmin)) + `,"treasury":` + q(string(treasury)) + `,"create_fee_ugnot":` + q(strconv.FormatInt(createFee, 10)) + `,"paused":` + strconv.FormatBool(paused) + `}` }
  20. #20gnomod.toml
  21. #21module = "gno.land/r/samcrew/memba_notes_v1" gno = "0.9"
  22. #22gov.gno
  23. #23package memba_notes_v1 import ( "chain" "chain/runtime" "crypto/sha256" "encoding/binary" "encoding/hex" "gno.land/p/samcrew/daoauth" gov "gno.land/r/samcrew/memba_gov" "strconv" ) const GovRealmPath = "gno.land/r/samcrew/memba_gov" var govAddress = chain.PackageAddress(GovRealmPath) func govMember(who address) bool { return gov.IsMember(who) } func SetGovWriters(cur realm, id [16]byte, baseRevision uint64, on bool, opID [16]byte) { who := enter(cur, false) defer leave() mutateSetGovWriters(cur, who, id, baseRevision, on, opID) } func mutateSetGovWriters(cur realm, who address, id [16]byte, baseRevision uint64, on bool, opID [16]byte) { n := live(id) owner(n, who) expected(n, baseRevision) if n.Owner != govAddress { panic("notes: gov owner required") } operation(n, opID) n.GovWriters = on bump(n, who, opID, "gov-writers") } func validGovAction(action string) bool { switch action { case "AcceptOwnership", "Commit", "Rename", "Rotate", "ApplyAccess", "Publish", "Reveal", "Delete", "AddWriter", "RemoveWriter", "ProposeOwner", "SetPolicy", "SetCommentMode", "SetPublicWrites", "SetListed", "ModerateListing", "SetGovWriters", "HideComment", "ResolveComment", "RejectAccessRequest", "Purge": return true } return false } // The typed payload is bounded and hashed as bytes, including its version. // Every precondition and opID is domain-bound; no raw large/Unicode content is // put in daoauth's 1024-byte printable-ASCII argument envelope. func govDigest(n *note, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) string { if !validGovAction(action) || len(payload) < 1 || len(payload) > MaxGovPayloadBytes { panic("notes: unknown/oversize gov operation") } b := appendText(nil, "memba-notes/v1/gov-operation") b = appendText(b, runtime.ChainID()) b = appendText(b, RealmPath) b = append(b, n.ID[:]...) number := make([]byte, 8) binary.BigEndian.PutUint64(number, ownerGeneration) b = append(b, number...) binary.BigEndian.PutUint64(number, baseRevision) b = append(b, number...) ep := make([]byte, 4) binary.BigEndian.PutUint32(ep, expectedEpoch) b = append(b, ep...) b = append(b, n.Mode) b = appendText(b, string(n.Owner)) b = appendText(b, string(n.PendingOwner)) b = appendText(b, string(n.Commitment)) b = appendText(b, action) hash := sha256.Sum256(payload) b = append(b, hash[:]...) b = append(b, opID[:]...) digest := sha256.Sum256(b) return hex.EncodeToString(digest[:]) } func GovDigest(idHex string, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) string { return govDigest(find(readID(idHex)), baseRevision, ownerGeneration, expectedEpoch, action, payload, opID) } func GovApply(cur realm, pid uint64, id [16]byte, baseRevision, ownerGeneration uint64, expectedEpoch uint32, action string, payload []byte, opID [16]byte) { executor := enter(cur, false) defer leave() if !cur.Previous().IsUserCall() { panic("notes: direct governance executor required") } operation(nil, opID) n := find(id) expected(n, baseRevision) if ownerGeneration != n.OwnerGeneration || expectedEpoch != n.Epoch { panic("notes: stale governance generation/epoch") } if action == "AcceptOwnership" { if n.PendingOwner != govAddress { panic("notes: pending gov owner required") } } else if n.Owner != govAddress && !((action == "ModerateListing" || action == "HideComment") && moderator(govAddress)) { panic("notes: gov owner required") } digest := govDigest(n, baseRevision, ownerGeneration, expectedEpoch, action, payload, opID) // Failure in dispatch also rolls back Consume in the same transaction. gov.Consume(cross(cur), pid, "notes."+action, daoauth.New().Str(digest).String(), "note:"+idText(id), gov.Routine, executor) historyGov = &historyCall{executor, pid, opID, action} defer func() { historyGov = nil }() dispatchGov(cur, n, baseRevision, expectedEpoch, action, payload, opID) if n.Revision > baseRevision && n.Last.Operation == opID { n.LastExecutor = executor } chain.Emit("NoteGoverned", "id", idText(id), "action", action, "pid", strconv.FormatUint(pid, 10), "executor", string(executor), "op_id", idText(opID)) } func dispatchGov(cur realm, n *note, rev uint64, epoch uint32, action string, payload []byte, op [16]byte) { r := payloadReader(payload) id := n.ID who := govAddress switch action { case "AcceptOwnership": r.done() mutateAcceptOwnership(cur, who, id, rev, op) case "Commit": mask := r.byte() title := r.blob(MaxCipherTitleBytes) body := r.blob(MaxCipherBodyBytes) r.done() mutateCommit(cur, who, id, rev, epoch, mask, title, body, op) case "Rename": title := r.blob(MaxCipherTitleBytes) r.done() mutateCommit(cur, who, id, rev, epoch, 1, title, nil, op) case "Rotate": mode := r.byte() title := r.blob(MaxCipherTitleBytes) body := r.blob(MaxCipherBodyBytes) manifest := r.blob(MaxManifestBytes) kc := r.blob(32) r.done() mutateRotate(cur, who, id, rev, epoch, mode, title, body, manifest, kc, op) case "ApplyAccess": mode := r.byte() manifest := r.blob(MaxManifestBytes) r.done() mutateApplyAccess(cur, who, id, rev, epoch, mode, manifest, op) case "Publish": mode := r.byte() title := r.blob(MaxTitleBytes) body := r.blob(MaxBodyBytes) r.done() mutatePublish(cur, who, id, rev, mode, title, body, op) case "Reveal": ep := r.u32() key := r.take(32) r.done() mutateReveal(cur, who, id, rev, ep, key, op) case "Delete": r.done() mutateDelete(cur, who, id, rev, op) case "AddWriter", "RemoveWriter", "ProposeOwner": to := address(string(r.blob(90))) r.done() if action == "AddWriter" { mutateAddWriter(cur, who, id, rev, to, op) } else if action == "RemoveWriter" { mutateRemoveWriter(cur, who, id, rev, to, op) } else { mutateProposeOwner(cur, who, id, rev, to, op) } case "SetPolicy": kind := r.byte() key := string(r.blob(233)) r.done() mutateSetPolicy(cur, who, id, rev, kind, key, op) case "SetPublicWrites": on := r.boolean() r.done() mutateSetPublicWrites(cur, who, id, rev, on, op) case "SetCommentMode": on := r.boolean() r.done() mutateSetCommentMode(cur, who, id, rev, on, op) case "SetListed": on := r.boolean() r.done() mutateSetListed(cur, who, id, rev, on, op) case "ModerateListing": on := r.boolean() r.done() mutateModerateListing(cur, who, id, rev, on, op) case "SetGovWriters": on := r.boolean() r.done() mutateSetGovWriters(cur, who, id, rev, on, op) case "HideComment", "ResolveComment": cid := r.id() commentRev := r.u64() on := r.boolean() r.done() if action == "HideComment" { mutateHideComment(cur, who, id, cid, commentRev, on, op) } else { mutateResolveComment(cur, who, id, cid, commentRev, on, op) } case "RejectAccessRequest": to := address(string(r.blob(90))) priorOp := r.id() r.done() v, ok := requests.Get(requestKey(id, to)) if !ok || v.(*accessRequest).Last.Operation != priorOp { panic("notes: stale request approval") } mutateRejectAccessRequest(cur, who, id, to, op) case "Purge": limit := r.byte() r.done() mutatePurge(cur, who, id, int(limit)) default: panic("notes: unknown governance action") } }
  24. #24gov_codec.gno

Result log

msg:0,success:true,log:,events:[]