1// Package quotes is a community "Quote of the Moment" board realm.2//3// Anyone can submit a quote via the exported crossing function AddQuote.4// Each quote records its text, the address of the caller who added it, and5// the block height at submission time. Render deterministically picks a6// "featured" quote by the current block height (a stable index into the7// list), shows it as a blockquote, then lists every quote with its author8// and id below.9package quotes1011import (12 "strconv"13 "strings"1415 "chain"16 "chain/runtime"17)1819// quote is a single board entry. Author is stored as a plain string address20// (realm values are ephemeral and must never be persisted).21type quote struct {22 id int23 text string24 author string // bech32 address of the submitter, or "" for seeded quotes25 height int64 // block height at submission (0 for seeded quotes)26}2728// board is the persistent realm state. A plain slice is fine here: the list29// only ever grows by append and Render walks it in insertion order, which is30// deterministic.31var board []quote3233// init seeds the board so a fresh deploy renders nicely.34func init() {35 seed := []string{36 "The unexamined life is not worth living.",37 "We suffer more often in imagination than in reality.",38 "Waste no more time arguing about what a good person should be. Be one.",39 "He who is brave is free.",40 }41 for _, s := range seed {42 board = append(board, quote{43 id: len(board),44 text: s,45 author: "", // seeded46 height: 0,47 })48 }49}5051// AddQuote is the exported, state-mutating entry point. It follows the gno 0.952// interrealm crossing convention: the first parameter is `cur realm`. Callers53// invoke it as AddQuote(cross(cur), "...") over a MsgCall.54func AddQuote(cur realm, text string) {55 // Authentication primitive: reject a stale/forged realm value before56 // deriving any caller identity from it.57 if !cur.IsCurrent() {58 panic("spoofed realm: cur is not the live crossing frame")59 }6061 if err := validate(text); err != nil {62 panic(err)63 }6465 author := cur.Previous().Address().String()66 h := runtime.ChainHeight()6768 board = append(board, quote{69 id: len(board),70 text: text,71 author: author,72 height: h,73 })7475 chain.Emit("QuoteAdded",76 "id", strconv.Itoa(len(board)-1),77 "author", author,78 "height", strconv.FormatInt(h, 10),79 )80}8182// maxQuoteLen caps quote length to keep storage costs bounded and Render tidy.83const maxQuoteLen = 2808485// validate enforces basic content rules. Returns an error describing the86// problem, or nil when the text is acceptable.87func validate(text string) error {88 if len(text) == 0 {89 return errEmpty90 }91 if len(text) > maxQuoteLen {92 return errTooLong93 }94 return nil95}9697// Sentinel errors for validation. Kept as package-level vars so tests can98// compare against them directly.99var (100 errEmpty = quoteError("quote text must not be empty")101 errTooLong = quoteError("quote text exceeds " + strconv.Itoa(maxQuoteLen) + " characters")102)103104// quoteError is a trivial string-backed error type (the VM has no host105// stdlib errors package convenience beyond this).106type quoteError string107108func (e quoteError) Error() string { return string(e) }109110// featuredIndex deterministically picks which quote is "of the moment" for a111// given block height. Pure function of (height, count) so it is fully112// deterministic and easy to test.113func featuredIndex(height int64, count int) int {114 if count <= 0 {115 return -1116 }117 // Guard against a negative height (should not happen on-chain, but keeps118 // the modulo well-defined).119 if height < 0 {120 height = -height121 }122 return int(height % int64(count))123}124125// Render produces the Markdown page shown in gnoweb. It is NOT a crossing126// function (no `cur realm` parameter) — it is a read-only view.127func Render(path string) string {128 count := len(board)129 if count == 0 {130 return "# Quote of the Moment\n\n_No quotes yet. Be the first to add one._\n"131 }132133 h := runtime.ChainHeight()134 idx := featuredIndex(h, count)135 featured := board[idx]136137 var b strings.Builder138 b.WriteString("# Quote of the Moment\n\n")139140 // Featured quote as a blockquote.141 b.WriteString("> ")142 b.WriteString(featured.text)143 b.WriteString("\n>\n> — ")144 b.WriteString(authorLabel(featured))145 b.WriteString(" (#")146 b.WriteString(strconv.Itoa(featured.id))147 b.WriteString(")\n\n")148149 b.WriteString("_Featured pick rotates with block height ")150 b.WriteString(strconv.FormatInt(h, 10))151 b.WriteString(" — index ")152 b.WriteString(strconv.Itoa(idx))153 b.WriteString(" of ")154 b.WriteString(strconv.Itoa(count))155 b.WriteString("._\n\n")156157 // Full list.158 b.WriteString("## All quotes (")159 b.WriteString(strconv.Itoa(count))160 b.WriteString(")\n\n")161 for _, q := range board {162 b.WriteString("- **#")163 b.WriteString(strconv.Itoa(q.id))164 b.WriteString("** ")165 b.WriteString(q.text)166 b.WriteString(" — _")167 b.WriteString(authorLabel(q))168 b.WriteString("_\n")169 }170171 return b.String()172}173174// authorLabel renders a human-friendly author string. Seeded quotes (empty175// author) show as the house account.176func authorLabel(q quote) string {177 if q.author == "" {178 return "the board"179 }180 return q.author181}182AddQuote(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, text string)
Render(path string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.