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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

store.gno

5.11 Kb · 169 lines
  1package app
  2
  3import "strconv"
  4
  5// kv is the schema-free key/value data itself. It is unexported and never
  6// handed out: callers reach it only through a [Store], which gates every
  7// operation. Keeping the data and the accessor separate is what lets the
  8// accessor be a revocable capability while the data persists untouched.
  9//
 10// It exists because a struct's fields are as permanent as the realm that
 11// declares them: if your state is `type State struct { count int64 }` in a
 12// realm you can never redeploy, you can never add a second field. Keys are
 13// data, so a kv can grow forever. The trade is everything a type gives you:
 14// nothing is checked, values are strings, and a typo in a key is a silent
 15// empty read.
 16//
 17// # Where the writes land
 18//
 19// A kv is allocated by your realm, so Gno's storage-realm borrow persists
 20// every write in the realm that owns it -- not in the caller's. That is what
 21// lets an implementation realm mutate your state without holding any of it.
 22type kv struct {
 23	keys []string // sorted
 24	vals []string
 25}
 26
 27func newKV() *kv { return &kv{} }
 28
 29// find returns the insertion index for k and whether k is present.
 30func (s *kv) find(k string) (int, bool) {
 31	lo, hi := 0, len(s.keys)
 32	for lo < hi {
 33		mid := int(uint(lo+hi) >> 1)
 34		if s.keys[mid] < k {
 35			lo = mid + 1
 36		} else {
 37			hi = mid
 38		}
 39	}
 40	return lo, lo < len(s.keys) && s.keys[lo] == k
 41}
 42
 43func (s *kv) get(k string) string {
 44	if i, ok := s.find(k); ok {
 45		return s.vals[i]
 46	}
 47	return ""
 48}
 49
 50func (s *kv) has(k string) bool {
 51	_, ok := s.find(k)
 52	return ok
 53}
 54
 55func (s *kv) set(k, v string) {
 56	i, ok := s.find(k)
 57	if ok {
 58		s.vals[i] = v
 59		return
 60	}
 61	s.keys = append(s.keys, "")
 62	s.vals = append(s.vals, "")
 63	copy(s.keys[i+1:], s.keys[i:])
 64	copy(s.vals[i+1:], s.vals[i:])
 65	s.keys[i], s.vals[i] = k, v
 66}
 67
 68func (s *kv) del(k string) bool {
 69	i, ok := s.find(k)
 70	if !ok {
 71		return false
 72	}
 73	s.keys = append(s.keys[:i], s.keys[i+1:]...)
 74	s.vals = append(s.vals[:i], s.vals[i+1:]...)
 75	return true
 76}
 77
 78func (s *kv) length() int { return len(s.keys) }
 79
 80func (s *kv) allKeys() []string {
 81	dup := make([]string, len(s.keys))
 82	copy(dup, s.keys)
 83	return dup
 84}
 85
 86func (s *kv) keysWithPrefix(prefix string) []string {
 87	out := []string{}
 88	i, _ := s.find(prefix)
 89	for ; i < len(s.keys); i++ {
 90		k := s.keys[i]
 91		if len(k) < len(prefix) || k[:len(prefix)] != prefix {
 92			break
 93		}
 94		out = append(out, k)
 95	}
 96	return out
 97}
 98
 99func (s *kv) getInt(k string) int64 {
100	n, err := strconv.ParseInt(s.get(k), 10, 64)
101	if err != nil {
102		return 0
103	}
104	return n
105}
106
107func (s *kv) setInt(k string, v int64) { s.set(k, strconv.FormatInt(v, 10)) }
108
109func (s *kv) addInt(k string, delta int64) int64 {
110	n := s.getInt(k) + delta
111	s.setInt(k, n)
112	return n
113}
114
115// Store is the handler-facing accessor. It is NOT the data -- it is a
116// permission to touch it, bound to the realm value it was issued for.
117//
118// This is what closes the capability leak. The old design handed out a raw
119// pointer to the data, which a handler could stash in a package variable and
120// keep using after it was rolled back or the realm was frozen. A Store holds
121// the caller's realm value and re-checks it on every operation, so:
122//
123//   - It cannot be stashed. Assigning it to realm state persists the realm
124//     value it carries, which the VM refuses ("cannot persist realm value"),
125//     aborting the transaction. The capability cannot outlive the call.
126//   - It cannot be replayed. Even held transiently, a Store whose frame has
127//     returned fails IsCurrent() on its next use.
128//
129// So the grant lives and dies with the exact call frame that obtained it,
130// which is the only window in which the caller is genuinely the live handler
131// (or a registered extension). See [App.Store].
132type Store struct {
133	app *App
134	rlm realm
135}
136
137// Get returns the value at k, or "" if absent. An absent key and a key set to
138// "" read the same; use Has to tell them apart.
139func (s *Store) Get(k string) string { return s.app.access(0, s.rlm).get(k) }
140
141// Has reports whether k is present.
142func (s *Store) Has(k string) bool { return s.app.access(0, s.rlm).has(k) }
143
144// Set writes v at k.
145func (s *Store) Set(k, v string) { s.app.access(0, s.rlm).set(k, v) }
146
147// Delete removes k, reporting whether it was there.
148func (s *Store) Delete(k string) bool { return s.app.access(0, s.rlm).del(k) }
149
150// Len returns the number of keys.
151func (s *Store) Len() int { return s.app.access(0, s.rlm).length() }
152
153// Keys returns a copy of the keys, in order.
154func (s *Store) Keys() []string { return s.app.access(0, s.rlm).allKeys() }
155
156// KeysWithPrefix returns the keys under a prefix, in order. Prefixes are how
157// a Store holds more than one collection: "task/1", "task/2", "meta/owner".
158func (s *Store) KeysWithPrefix(prefix string) []string {
159	return s.app.access(0, s.rlm).keysWithPrefix(prefix)
160}
161
162// GetInt reads k as an int64, returning 0 if it is absent or unparsable.
163func (s *Store) GetInt(k string) int64 { return s.app.access(0, s.rlm).getInt(k) }
164
165// SetInt writes an int64 at k.
166func (s *Store) SetInt(k string, v int64) { s.app.access(0, s.rlm).setInt(k, v) }
167
168// AddInt adds delta to k and returns the new value.
169func (s *Store) AddInt(k string, delta int64) int64 { return s.app.access(0, s.rlm).addInt(k, delta) }