state.gno
3.29 Kb · 129 lines
1package memba_notes_v1
2
3import (
4 "chain"
5 "chain/runtime"
6 "encoding/hex"
7 "strconv"
8 "strings"
9 "unicode"
10 "unicode/utf8"
11)
12
13func enter(cur realm, payable bool) address {
14 if !cur.IsCurrent() {
15 panic("notes: stale realm")
16 }
17 if busy {
18 panic("notes: reentry")
19 }
20 if !payable {
21 assertNoCoins()
22 }
23 busy = true
24 return cur.Previous().Address()
25}
26func leave() { busy = false }
27func idText(id [16]byte) string { return hex.EncodeToString(id[:]) }
28func readID(s string) [16]byte {
29 if len(s) != 32 {
30 panic("notes: bad note id")
31 }
32 b, err := hex.DecodeString(s)
33 if err != nil || hex.EncodeToString(b) != s {
34 panic("notes: bad note id")
35 }
36 var id [16]byte
37 copy(id[:], b)
38 return id
39}
40func find(id [16]byte) *note {
41 v, ok := notes.Get(idText(id))
42 if !ok {
43 panic("notes: note not found")
44 }
45 return v.(*note)
46}
47func live(id [16]byte) *note {
48 n := find(id)
49 if n.Deleted {
50 panic("notes: note deleted")
51 }
52 return n
53}
54func expected(n *note, rev uint64) {
55 if rev != n.Revision {
56 panic("notes: stale revision")
57 }
58 if rev == ^uint64(0) {
59 panic("notes: revision exhausted")
60 }
61}
62func owner(n *note, who address) {
63 if n.Owner != who {
64 panic("notes: owner required")
65 }
66}
67func operation(n *note, op [16]byte) {
68 if op == ([16]byte{}) {
69 panic("notes: empty operation id")
70 }
71 if n != nil && n.Last.Operation == op {
72 panic("notes: operation id reused")
73 }
74}
75func bump(n *note, who address, op [16]byte, action string) {
76 bumpHistory(n, who, op, action, "")
77}
78func bumpHistory(n *note, who address, op [16]byte, action, historyExtra string) {
79 operation(n, op)
80 n.Revision++
81 n.Last = receipt{op, who, runtime.ChainHeight()}
82 n.LastExecutor = ""
83 historyNote(n, who, op, action, historyExtra)
84 chain.Emit("NoteChanged", "id", idText(n.ID), "action", action, "revision", strconv.FormatUint(n.Revision, 10), "actor", string(who), "op_id", idText(op))
85}
86func copied(b []byte) []byte { return append([]byte(nil), b...) }
87func public(mode uint8) bool { return mode == ModePublic || mode == ModePublicOpen }
88func indexKey(n *note) string {
89 s := strconv.FormatUint(n.Sequence, 10)
90 return strings.Repeat("0", 20-len(s)) + s + ":" + idText(n.ID)
91}
92func index(n *note) {
93 for _, a := range append(copiedAddresses(n.Writers), n.Readers...) {
94 sharedIndex.Set(string(a)+":"+indexKey(n), idText(n.ID))
95 }
96 ownerIndex.Set(string(n.Owner)+":"+indexKey(n), idText(n.ID))
97 if public(n.Mode) && n.Listed && !n.ModeratorHidden {
98 publicIndex.Set(indexKey(n), idText(n.ID))
99 }
100}
101func unindex(n *note) {
102 for _, a := range append(copiedAddresses(n.Writers), n.Readers...) {
103 sharedIndex.Remove(string(a) + ":" + indexKey(n))
104 }
105 ownerIndex.Remove(string(n.Owner) + ":" + indexKey(n))
106 publicIndex.Remove(indexKey(n))
107}
108func validateTitle(title []byte) {
109 if len(title) == 0 || len(title) > MaxTitleBytes || !utf8.Valid(title) || utf8.RuneCount(title) > 80 {
110 panic("notes: invalid title")
111 }
112 s := string(title)
113 if strings.TrimSpace(s) != s {
114 panic("notes: invalid title")
115 }
116 for _, r := range s {
117 if r == '/' || unicode.IsControl(r) || unicode.Is(unicode.Cf, r) {
118 panic("notes: invalid title")
119 }
120 }
121}
122func validatePublic(title, body []byte) {
123 validateTitle(title)
124 if len(body) > MaxBodyBytes || !utf8.Valid(body) {
125 panic("notes: invalid body")
126 }
127}
128
129func copiedAddresses(a []address) []address { return append([]address(nil), a...) }