Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

vault.gno

3.82 Kb · 147 lines
  1package vault
  2
  3import (
  4	"chain"
  5	"strconv"
  6	"strings"
  7
  8	"gno.land/p/moul/kit/ui/v0"
  9	"gno.land/p/nt/avl/v0"
 10)
 11
 12// selfPath is this realm's own page. A root-relative link resolves against
 13// the domain, not the realm, so every link back into it names it in full.
 14const selfPath = "/r/moul/x/daily/vault/v1"
 15
 16// vaults maps address string -> *avl.Tree (key string -> value string).
 17var vaults avl.Tree
 18
 19// The bounds on what a caller may store. Anyone may open a vault, the root
 20// page lists every vault, and a vault's page lists every entry.
 21const (
 22	MaxVaults   = 1000
 23	MaxEntries  = 100 // per vault
 24	MaxKeyLen   = 64
 25	MaxValueLen = 512
 26)
 27
 28// vaultFor returns the caller's namespace tree, creating it if needed.
 29func vaultFor(owner address) *avl.Tree {
 30	k := owner.String()
 31	if v := vaults.Get(k); v != nil {
 32		return v.(*avl.Tree)
 33	}
 34	if vaults.Size() >= MaxVaults {
 35		panic("vault is full at " + strconv.Itoa(MaxVaults) + " vaults")
 36	}
 37	t := &avl.Tree{}
 38	vaults.Set(k, t)
 39	return t
 40}
 41
 42// Set stores value under key in the caller's own namespace.
 43func Set(cur realm, key string, value string) {
 44	who := caller(cur)
 45	if key == "" {
 46		panic("key must not be empty")
 47	}
 48	if len(key) > MaxKeyLen || len(value) > MaxValueLen {
 49		panic("key or value too long")
 50	}
 51	t := vaultFor(who)
 52	if !t.Has(key) && t.Size() >= MaxEntries {
 53		panic("a vault holds at most " + strconv.Itoa(MaxEntries) + " entries")
 54	}
 55	t.Set(key, value)
 56	chain.Emit("VaultSet", "owner", who.String(), "key", key)
 57}
 58
 59// Delete removes key from the caller's own namespace.
 60func Delete(cur realm, key string) {
 61	who := caller(cur)
 62	k := who.String()
 63	v := vaults.Get(k)
 64	if v == nil {
 65		panic("no vault for caller")
 66	}
 67	t := v.(*avl.Tree)
 68	if _, removed := t.Remove(key); !removed {
 69		panic("key not found")
 70	}
 71	if t.Size() == 0 {
 72		vaults.Remove(k)
 73	}
 74	chain.Emit("VaultDelete", "owner", who.String(), "key", key)
 75}
 76
 77// Render shows all vaults (root) or a single address's entries ("/<address>").
 78func Render(path string) string {
 79	p := strings.TrimPrefix(path, "/")
 80	if p == "" {
 81		return renderRoot()
 82	}
 83	return renderAddress(p)
 84}
 85
 86func renderRoot() string {
 87	var b strings.Builder
 88	b.WriteString("# Key-Value Vault\n\n")
 89	b.WriteString("A per-address key-value store. Each caller owns their own namespace; only the owner can mutate their entries.\n\n")
 90	if vaults.Size() == 0 {
 91		b.WriteString("_No vaults yet. Call `Set(cur, key, value)` to create one._\n")
 92		return b.String()
 93	}
 94	b.WriteString("## Vaults\n\n")
 95	b.WriteString("| Address | Entries |\n")
 96	b.WriteString("|---|---|\n")
 97	vaults.Iterate("", "", func(k string, v interface{}) bool {
 98		t := v.(*avl.Tree)
 99		b.WriteString("| [")
100		b.WriteString(k)
101		b.WriteString("](" + selfPath + ":")
102		// link back into this realm's render for the address subpage
103		b.WriteString(k)
104		b.WriteString(") | ")
105		b.WriteString(strconv.Itoa(t.Size()))
106		b.WriteString(" |\n")
107		return false
108	})
109	b.WriteString("\nView an address's entries at path `/<address>`.\n")
110	return b.String()
111}
112
113func renderAddress(addr string) string {
114	var b strings.Builder
115	b.WriteString("# Vault: ")
116	b.WriteString(ui.Inline(addr))
117	b.WriteString("\n\n")
118	v := vaults.Get(addr)
119	if v == nil {
120		b.WriteString("_No vault for this address._\n")
121		return b.String()
122	}
123	t := v.(*avl.Tree)
124	b.WriteString("| Key | Value |\n")
125	b.WriteString("|---|---|\n")
126	t.Iterate("", "", func(k string, val interface{}) bool {
127		b.WriteString("| ")
128		b.WriteString(ui.Cell(k))
129		b.WriteString(" | ")
130		b.WriteString(ui.Cell(val.(string)))
131		b.WriteString(" |\n")
132		return false
133	})
134	b.WriteString("\nTotal entries: ")
135	b.WriteString(strconv.Itoa(t.Size()))
136	b.WriteString("\n")
137	return b.String()
138}
139
140// caller is the address that crossed into this realm, after checking that the
141// realm token is this frame's own.
142func caller(cur realm) address {
143	if !cur.IsCurrent() {
144		panic("vault: spoofed realm")
145	}
146	return cur.Previous().Address()
147}