1package app23import "strconv"45// kv is the schema-free key/value data itself. It is unexported and never6// handed out: callers reach it only through a [Store], which gates every7// operation. Keeping the data and the accessor separate is what lets the8// 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 that11// declares them: if your state is `type State struct { count int64 }` in a12// realm you can never redeploy, you can never add a second field. Keys are13// 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 silent15// empty read.16//17// # Where the writes land18//19// A kv is allocated by your realm, so Gno's storage-realm borrow persists20// every write in the realm that owns it -- not in the caller's. That is what21// lets an implementation realm mutate your state without holding any of it.22type kv struct {23 keys []string // sorted24 vals []string25}2627func newKV() *kv { return &kv{} }2829// 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 + 136 } else {37 hi = mid38 }39 }40 return lo, lo < len(s.keys) && s.keys[lo] == k41}4243func (s *kv) get(k string) string {44 if i, ok := s.find(k); ok {45 return s.vals[i]46 }47 return ""48}4950func (s *kv) has(k string) bool {51 _, ok := s.find(k)52 return ok53}5455func (s *kv) set(k, v string) {56 i, ok := s.find(k)57 if ok {58 s.vals[i] = v59 return60 }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, v66}6768func (s *kv) del(k string) bool {69 i, ok := s.find(k)70 if !ok {71 return false72 }73 s.keys = append(s.keys[:i], s.keys[i+1:]...)74 s.vals = append(s.vals[:i], s.vals[i+1:]...)75 return true76}7778func (s *kv) length() int { return len(s.keys) }7980func (s *kv) allKeys() []string {81 dup := make([]string, len(s.keys))82 copy(dup, s.keys)83 return dup84}8586func (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 break93 }94 out = append(out, k)95 }96 return out97}9899func (s *kv) getInt(k string) int64 {100 n, err := strconv.ParseInt(s.get(k), 10, 64)101 if err != nil {102 return 0103 }104 return n105}106107func (s *kv) setInt(k string, v int64) { s.set(k, strconv.FormatInt(v, 10)) }108109func (s *kv) addInt(k string, delta int64) int64 {110 n := s.getInt(k) + delta111 s.setInt(k, n)112 return n113}114115// Store is the handler-facing accessor. It is NOT the data -- it is a116// 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 raw119// pointer to the data, which a handler could stash in a package variable and120// keep using after it was rolled back or the realm was frozen. A Store holds121// 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 realm124// 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 has127// 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 handler131// (or a registered extension). See [App.Store].132type Store struct {133 app *App134 rlm realm135}136137// Get returns the value at k, or "" if absent. An absent key and a key set to138// "" 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) }140141// Has reports whether k is present.142func (s *Store) Has(k string) bool { return s.app.access(0, s.rlm).has(k) }143144// Set writes v at k.145func (s *Store) Set(k, v string) { s.app.access(0, s.rlm).set(k, v) }146147// Delete removes k, reporting whether it was there.148func (s *Store) Delete(k string) bool { return s.app.access(0, s.rlm).del(k) }149150// Len returns the number of keys.151func (s *Store) Len() int { return s.app.access(0, s.rlm).length() }152153// Keys returns a copy of the keys, in order.154func (s *Store) Keys() []string { return s.app.access(0, s.rlm).allKeys() }155156// KeysWithPrefix returns the keys under a prefix, in order. Prefixes are how157// 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}161162// 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) }164165// SetInt writes an int64 at k.166func (s *Store) SetInt(k string, v int64) { s.app.access(0, s.rlm).setInt(k, v) }167168// 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) }170Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.