Realm
gno.land/r/moul/x/across/v0
Overview
Realm Path
gno.land/r/moul/x/across/v0
Exported Functions
3
State Entries
5
Source Files
3
Total Package Entries
10
Exported Functions
3 exported functions
State
5 state entries
Source Code
FILES
across.gno
go
1// Package across reads one address across fifteen other realms that are
2// already live, and renders everything they will say about it on one page.
3//
4// It is a composability demo with a working product hiding inside it. The
5// realms it reads were written months apart, by nobody coordinating, and none
6// of them knows this one exists. Not a line of them changed to make this
7// possible: the only requirement any of them had to meet was NOT declaring
8// `private = true`, which is what lets another realm import it at all.
9//
10// # Why this realm holds no state and extracts no library
11//
12// Every other realm here splits into a pure `p/` and a thin `r/`. This one
13// cannot, and that is the point: its entire content is the wiring. Fifteen
14// import lines, one call each, and a table. Extract a library and what is left
15// on both sides is empty.
16//
17// # What it measures, besides balances
18//
19// The signatures below are the honest half of the demo. Eight of these realms
20// hold a per-address balance and they agree on nothing:
21//
22// erc20, vestoken, wrapped BalanceOf(address) uint64
23// pairs/aaa, pairs/bbb BalanceOf(address) int64
24// grc20faucet BalanceOf(symbol string, owner address) int64
25// wikicoin BalanceOf(address) uint
26// dice RollsOf(addr string) []int
27//
28// Three integer widths, two signednesses, one that takes the address as a
29// string, one that needs a symbol first. Composition works anyway, because a
30// gno import is a type-checked contract with a permanent artifact behind it and
31// not a call convention anyone has to agree on in advance. It just costs one
32// hand-written adapter per source, which is what the report function is.
33//
34// # The two sources that could not be read
35//
36// - r/moul/x/amm/v0 exports SharesOf(keyA, keyB string, owner address), and
37// it routes through mustPool, which PANICS when the pool does not exist.
38// A getter that panics is not composable: fifteen sources on one page means
39// one panicking source takes the whole page down, and a wrapper cannot
40// recover, because recover() does not catch a cross-realm abort. So amm
41// contributes PoolCount() to the index and nothing to a report.
42// - r/moul/demo/vault/v0 exports MyBalance() with no address parameter. It
43// answers for whoever is calling, which from here is this realm, so the
44// value is structurally unreadable from outside.
45//
46// Neither is a bug in those realms. Both are what a getter looks like when
47// nobody has tried to call it from another realm yet.
48package across
49
50import (
51 "strconv"
52 "strings"
53
54 "gno.land/p/moul/kit/ui/v0"
55
56 "gno.land/r/moul/demo/wikicoin/v0"
57 "gno.land/r/moul/reactions/v0"
58 "gno.land/r/moul/x/amm/v0"
59 "gno.land/r/moul/x/daily/dice/v0"
60 "gno.land/r/moul/x/daily/erc20/v0"
61 "gno.land/r/moul/x/daily/lottery/v0"
62 "gno.land/r/moul/x/daily/ratelimitdemo/v0"
63 "gno.land/r/moul/x/daily/staking/v0"
64 "gno.land/r/moul/x/daily/streak/v0"
65 "gno.land/r/moul/x/daily/vestoken/v0"
66 "gno.land/r/moul/x/daily/wrapped/v0"
67 "gno.land/r/moul/x/grc20faucet/v0"
68 "gno.land/r/moul/x/pairs/aaa/v0"
69 "gno.land/r/moul/x/pairs/aaabbb/v0"
70 "gno.land/r/moul/x/pairs/bbb/v0"
71)
72
73// reactionPage is the page key handed to r/moul/reactions. Reactions are keyed
74// on an arbitrary page string rather than on a realm, so a reader of this
75// report is asked about the same page /u/moul shows.
76const reactionPage = "gno.land/r/moul/home"
77
78// Self is this realm's own package path, exported so a caller can build a link
79// back without hardcoding the version.
80const Self = "gno.land/r/moul/x/across/v0"
81
82// A fact is one answer from one source realm about one address.
83//
84// zero is carried separately from value because "0" and "none" are the common
85// answer and the table is worth reading only for the rows that are not that.
86type fact struct {
87 source string // package path, without the gno.land/ prefix
88 what string
89 value string
90 zero bool
91}
92
93// Report collects what every wired source says about who.
94//
95// Every call in here is a plain exported read on another realm: no cur realm,
96// no cross, nothing written. A read with no realm parameter is borrowed rather
97// than entered (AGENTS.md), so none of these realms even observes a caller.
98func Report(who address) []fact {
99 out := []fact{}
100
101 add := func(source, what string, n int64, unit string) {
102 v := strconv.FormatInt(n, 10)
103 if unit != "" {
104 v += " " + unit
105 }
106 out = append(out, fact{source: source, what: what, value: v, zero: n == 0})
107 }
108
109 // Balances. Eight sources, five signatures.
110 add("r/moul/x/daily/erc20/v0", "balance", int64(erc20.BalanceOf(who)), erc20.Symbol())
111 add("r/moul/x/daily/vestoken/v0", "liquid balance", int64(vestoken.BalanceOf(who)), vestoken.Symbol())
112 add("r/moul/x/daily/wrapped/v0", "wrapped balance", int64(wrapped.BalanceOf(who)), "WGNOT")
113 add("r/moul/demo/wikicoin/v0", "balance", int64(wikicoin.BalanceOf(who)), "WIKI")
114 add("r/moul/x/pairs/aaa/v0", "balance", aaa.BalanceOf(who), "AAA")
115 add("r/moul/x/pairs/bbb/v0", "balance", bbb.BalanceOf(who), "BBB")
116 add("r/moul/x/grc20faucet/v0", "balance", grc20faucet.BalanceOf("RED", who), "RED")
117 add("r/moul/x/grc20faucet/v0", "balance", grc20faucet.BalanceOf("BLUE", who), "BLUE")
118
119 // A grant is three numbers, so it does not fit the add helper.
120 total, claimed, claimable := vestoken.GrantOf(who)
121 out = append(out, fact{
122 source: "r/moul/x/daily/vestoken/v0",
123 what: "vesting grant",
124 value: strconv.FormatUint(total, 10) + " total, " +
125 strconv.FormatUint(claimed, 10) + " claimed, " +
126 strconv.FormatUint(claimable, 10) + " claimable",
127 zero: total == 0,
128 })
129
130 // Positions.
131 add("r/moul/x/pairs/aaabbb/v0", "LP shares", aaabbb.SharesOf(who), "")
132 add("r/moul/x/daily/staking/v0", "staked", int64(staking.StakedOf(who)), "")
133 add("r/moul/x/daily/staking/v0", "unclaimed rewards", int64(staking.Earned(who)), "")
134
135 // Activity.
136 add("r/moul/x/daily/streak/v0", "current streak", int64(streak.Current(who)), "day(s)")
137 add("r/moul/x/daily/streak/v0", "best streak", int64(streak.Best(who)), "day(s)")
138 add("r/moul/x/daily/dice/v0", "dice rolled", int64(len(dice.RollsOf(who.String()))), "")
139 add("r/moul/x/daily/ratelimitdemo/v0", "rate-limit tokens left", int64(ratelimitdemo.Tokens(who)), "")
140
141 entered := "no"
142 if lottery.HasEntered(who) {
143 entered = "yes, round " + strconv.Itoa(lottery.CurrentRound())
144 }
145 out = append(out, fact{
146 source: "r/moul/x/daily/lottery/v0",
147 what: "entered the lottery",
148 value: entered,
149 zero: !lottery.HasEntered(who),
150 })
151
152 // reactions answers with a palette key, already validated at write time, so
153 // it never carries caller markdown into this table.
154 react := reactions.ReactionOf(reactionPage, who)
155 shown := react
156 if shown == "" {
157 shown = "none"
158 }
159 out = append(out, fact{
160 source: "r/moul/reactions/v0",
161 what: "reaction on " + reactionPage,
162 value: shown,
163 zero: react == "",
164 })
165
166 return out
167}
168
169// sourceRealms is every realm this one imports for its data, in import order.
170// It is the single place the count comes from: the index table has one row per
171// entry and TestSourcesCoverReport checks that Report names nothing else.
172//
173// Fifteen entries, which is the number the prose says. TestSourceCount pins it.
174var sourceRealms = []string{
175 "r/moul/x/daily/erc20/v0",
176 "r/moul/x/daily/vestoken/v0",
177 "r/moul/x/daily/wrapped/v0",
178 "r/moul/demo/wikicoin/v0",
179 "r/moul/x/pairs/aaa/v0",
180 "r/moul/x/pairs/bbb/v0",
181 "r/moul/x/grc20faucet/v0",
182 "r/moul/x/pairs/aaabbb/v0",
183 "r/moul/x/amm/v0",
184 "r/moul/x/daily/staking/v0",
185 "r/moul/x/daily/streak/v0",
186 "r/moul/x/daily/dice/v0",
187 "r/moul/x/daily/lottery/v0",
188 "r/moul/x/daily/ratelimitdemo/v0",
189 "r/moul/reactions/v0",
190}
191
192// Sources reports how many realms this one reads.
193func Sources() int { return len(sourceRealms) }
194
195func Render(path string) string {
196 if path == "" {
197 return renderIndex()
198 }
199 addr := address(path)
200 if !addr.IsValid() {
201 return "# across\n\n" + ui.Empty(ui.Inline(ui.Short(path))+" is not a valid address.") +
202 "\n[back to the index](/r/moul/x/across/v0)\n"
203 }
204 return renderAddress(addr)
205}
206
207func renderAddress(who address) string {
208 var b strings.Builder
209 b.WriteString("# across: " + ui.AddrFull(who) + "\n\n")
210 b.WriteString("What " + strconv.Itoa(Sources()) + " independently deployed realms say about this address. ")
211 b.WriteString("None of them was changed to answer.\n\n")
212
213 facts := Report(who)
214
215 live := ui.NewTable("source", "fact", "value")
216 quiet := 0
217 for _, f := range facts {
218 if f.zero {
219 quiet++
220 continue
221 }
222 live.Row("[`"+f.source+"`](/"+f.source+")", f.what, f.value)
223 }
224
225 if live.Len() == 0 {
226 b.WriteString(ui.Empty("Every one of the " + strconv.Itoa(len(facts)) +
227 " reads came back empty. This address has never touched any of them."))
228 b.WriteString("\n")
229 } else {
230 b.WriteString("## Answers\n\n")
231 b.WriteString(live.String())
232 b.WriteString("\n")
233 b.WriteString(strconv.Itoa(quiet) + " of " + strconv.Itoa(len(facts)) +
234 " reads came back empty and are hidden.\n\n")
235 }
236
237 b.WriteString("[back to the index](/r/moul/x/across/v0)\n")
238 return b.String()
239}
240
241func renderIndex() string {
242 var b strings.Builder
243 b.WriteString("# across\n\n")
244 b.WriteString("One address, read across " + strconv.Itoa(Sources()) + " realms that are already on chain.\n\n")
245 b.WriteString("Open `/r/moul/x/across/v0:<address>` to get the report. ")
246 b.WriteString("Example: [/r/moul/x/across/v0:" + exampleAddr + "](/r/moul/x/across/v0:" + exampleAddr + ").\n\n")
247
248 b.WriteString("## Why this realm exists\n\n")
249 b.WriteString("A gno realm that does not declare `private = true` can be imported by any ")
250 b.WriteString("other realm, forever, at a path that can never be replaced. That makes every ")
251 b.WriteString("exported read on it a permanent public API. This realm is what happens when ")
252 b.WriteString("somebody actually uses " + strconv.Itoa(Sources()) + " of them at once.\n\n")
253 b.WriteString("Nothing here was negotiated. No registry, no interface, no shared base ")
254 b.WriteString("package, no version handshake, and no change to any source realm.\n\n")
255
256 b.WriteString("## The sources, and what each one is showing right now\n\n")
257 t := ui.NewTable("realm", "global reading")
258 t.Row("[`r/moul/x/daily/erc20/v0`](/r/moul/x/daily/erc20/v0)",
259 erc20.Symbol()+" supply "+strconv.FormatUint(erc20.TotalSupply(), 10))
260 t.Row("[`r/moul/x/daily/vestoken/v0`](/r/moul/x/daily/vestoken/v0)",
261 vestoken.Symbol()+" supply "+strconv.FormatUint(vestoken.TotalSupply(), 10))
262 t.Row("[`r/moul/x/daily/wrapped/v0`](/r/moul/x/daily/wrapped/v0)",
263 "wrapped supply "+strconv.FormatUint(wrapped.TotalSupply(), 10))
264 t.Row("[`r/moul/demo/wikicoin/v0`](/r/moul/demo/wikicoin/v0)", "per-address balances only")
265 t.Row("[`r/moul/x/pairs/aaa/v0`](/r/moul/x/pairs/aaa/v0)",
266 "AAA supply "+strconv.FormatInt(aaa.TotalSupply(), 10))
267 t.Row("[`r/moul/x/pairs/bbb/v0`](/r/moul/x/pairs/bbb/v0)",
268 "BBB supply "+strconv.FormatInt(bbb.TotalSupply(), 10))
269 t.Row("[`r/moul/x/grc20faucet/v0`](/r/moul/x/grc20faucet/v0)",
270 "RED supply "+strconv.FormatInt(grc20faucet.TotalSupply("RED"), 10)+
271 ", BLUE supply "+strconv.FormatInt(grc20faucet.TotalSupply("BLUE"), 10))
272 t.Row("[`r/moul/x/pairs/aaabbb/v0`](/r/moul/x/pairs/aaabbb/v0)",
273 "LP shares "+strconv.FormatInt(aaabbb.TotalShares(), 10))
274 t.Row("[`r/moul/x/amm/v0`](/r/moul/x/amm/v0)",
275 strconv.Itoa(amm.PoolCount())+" pool(s), index only (see below)")
276 t.Row("[`r/moul/x/daily/staking/v0`](/r/moul/x/daily/staking/v0)", "per-address stake and rewards")
277 t.Row("[`r/moul/x/daily/streak/v0`](/r/moul/x/daily/streak/v0)",
278 "day "+strconv.FormatInt(streak.Day(), 10))
279 t.Row("[`r/moul/x/daily/dice/v0`](/r/moul/x/daily/dice/v0)",
280 strconv.Itoa(dice.Total())+" roll(s) all time")
281 t.Row("[`r/moul/x/daily/lottery/v0`](/r/moul/x/daily/lottery/v0)",
282 "round "+strconv.Itoa(lottery.CurrentRound())+", "+
283 strconv.Itoa(lottery.NumEntrants())+" entrant(s)")
284 t.Row("[`r/moul/x/daily/ratelimitdemo/v0`](/r/moul/x/daily/ratelimitdemo/v0)", "per-address bucket")
285 t.Row("[`r/moul/reactions/v0`](/r/moul/reactions/v0)",
286 strconv.Itoa(reactions.Pages())+" page(s) reacted to")
287 b.WriteString(t.String())
288 b.WriteString("\n")
289
290 b.WriteString("## What it cost\n\n")
291 b.WriteString("Eight of these hold a per-address balance, and they share no signature: ")
292 b.WriteString("`uint64` three times, `int64` twice, `uint` once, one that wants a symbol ")
293 b.WriteString("before the address, one that wants the address as a `string`. Every source ")
294 b.WriteString("therefore needs its own adapter line. That is the whole cost, and it is paid ")
295 b.WriteString("once by the reader rather than up front by fifteen authors.\n\n")
296
297 b.WriteString("## The two that could not be read\n\n")
298 b.WriteString("- `r/moul/x/amm/v0` routes `SharesOf` through a helper that panics on an ")
299 b.WriteString("unknown pool. One panicking source takes down a page built from " +
300 strconv.Itoa(Sources()) + ", and ")
301 b.WriteString("`recover()` does not catch a cross-realm abort, so only its `PoolCount()` is ")
302 b.WriteString("safe to call from here.\n")
303 b.WriteString("- `r/moul/demo/vault/v0` exports `MyBalance()` with no address parameter. It ")
304 b.WriteString("answers for its caller, which from here is this realm.\n\n")
305 b.WriteString("Neither is a bug. Both are what a getter looks like before anyone has tried ")
306 b.WriteString("to call it from another realm.\n")
307
308 return b.String()
309}
310
311// exampleAddr is moul's address, used only to give the index a link that
312// resolves to a real report instead of an empty one.
313const exampleAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5"
314Raw Package Data
Raw JSON data