PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/social/threads/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / social / threads
Files
5 (README)(gnomod.toml)
Exported functions
18
Module
gno.land/r/moul/x/social/threads/v0
gno
0.9
This realm implements a recognized token.THREAD →

Files (5)

  • README.mdmarkdown
  • gnomod.tomltoml
  • render.gnogno
  • threads.gnogno
  • threads_test.gnogno
threads_test.gnogno
1package threads23import (4	"strconv"5	"strings"6	"testing"78	sc "gno.land/p/moul/x/social/coin/v0"9	th "gno.land/p/moul/x/social/threads/v0"10	"gno.land/p/nt/testutils/v0"11	"gno.land/p/nt/uassert/v0"12	"gno.land/p/nt/urequire/v0"13)1415const homePage = "gno.land/r/moul/home"1617var (18	alice = testutils.TestAddress("alice")19	bob   = testutils.TestAddress("bob")20	carol = testutils.TestAddress("carol")21)2223// reset clears realm state. Realm globals live for the whole test binary, so24// everything a later test reads, including the ledger, starts here.25//26// The ledger is rebuilt through a same-realm cross because grc20.NewToken27// needs a code-realm frame, which a test's EOA-origin cur is not. It is NOT28// re-registered with grc20reg: the registry is another realm and its entry29// survives this one's state.30func reset(cur realm) {31	board = th.NewBoard()32	func(cur realm) { // the name is forced: a realm argument must be called cur33		points = sc.New("Thread Points", "THREAD", 0, 0, tokenPolicy, 0, cur)34	}(cross(cur))35}3637// earn gives who n THREAD the only way the realm allows: n distinct addresses38// replying to a thread they wrote.39func earn(cur realm, t *testing.T, who address, n int) int64 {40	t.Helper()41	testing.SetRealm(testing.NewUserRealm(who))42	id := Post(cross(cur), homePage, "a thread by "+who.String())43	for i := 0; i < n; i++ {44		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("replier" + strconv.Itoa(i))))45		Reply(cross(cur), id, "hello")46	}47	return id48}4950func TestAnAuthorEarnsPeopleAndNotMessages(cur realm, t *testing.T) {51	reset(cur)5253	testing.SetRealm(testing.NewUserRealm(alice))54	id := Post(cross(cur), homePage, "first thread")55	uassert.Equal(t, int64(0), BalanceOf(alice), "writing earns nothing on its own")5657	testing.SetRealm(testing.NewUserRealm(bob))58	Reply(cross(cur), id, "nice")59	uassert.Equal(t, int64(1), BalanceOf(alice))6061	testing.SetRealm(testing.NewUserRealm(bob))62	Reply(cross(cur), id, "still me")63	uassert.Equal(t, int64(1), BalanceOf(alice), "one person is one point, however much they say")6465	testing.SetRealm(testing.NewUserRealm(alice))66	Reply(cross(cur), id, "thanks")67	uassert.Equal(t, int64(1), BalanceOf(alice), "replying to yourself is not an audience")6869	testing.SetRealm(testing.NewUserRealm(carol))70	Reply(cross(cur), id, "hi")71	uassert.Equal(t, int64(2), BalanceOf(alice))7273	uassert.Equal(t, 2, Repliers(id))74	uassert.Equal(t, int64(2), TotalSupply())75	uassert.Equal(t, int64(0), BalanceOf(bob), "a replier earns nothing")76}7778func TestBoostIsTheOnlyThingThatBurns(cur realm, t *testing.T) {79	reset(cur)80	id := earn(cur, t, alice, 10)81	urequire.Equal(t, int64(10), BalanceOf(alice))8283	testing.SetRealm(testing.NewUserRealm(alice))84	Boost(cross(cur), id)8586	uassert.Equal(t, int64(0), BalanceOf(alice))87	uassert.Equal(t, int64(0), TotalSupply(), "the sink removes supply, it does not move it")88	uassert.Equal(t, int64(123+PinBlocks), PinnedUntil(id), "tests start at height 123")89}9091func TestBoostRefusesWhatTheCallerCannotAfford(cur realm, t *testing.T) {92	reset(cur)93	id := earn(cur, t, alice, 1)9495	testing.SetRealm(testing.NewUserRealm(alice))96	uassert.AbortsContains(t, cur, "balance 1 is short of 10", func() {97		Boost(cross(cur), id)98	})99	uassert.Equal(t, int64(0), PinnedUntil(id), "a refused boost pins nothing")100	uassert.Equal(t, int64(1), BalanceOf(alice))101}102103// A second boost extends from the current pin, not from now, so the thread104// never loses time it was already paid for.105func TestASecondBoostExtendsRatherThanRestarts(cur realm, t *testing.T) {106	reset(cur)107	id := earn(cur, t, alice, 20)108109	testing.SetRealm(testing.NewUserRealm(alice))110	Boost(cross(cur), id)111	first := PinnedUntil(id)112113	testing.SetRealm(testing.NewUserRealm(alice))114	Boost(cross(cur), id)115	uassert.Equal(t, first+PinBlocks, PinnedUntil(id))116	uassert.Equal(t, int64(0), BalanceOf(alice))117}118119// Anyone may boost anyone's thread, and the buyer has to get THREAD from120// somebody who earned it. That is the whole market.121func TestPlacementIsBoughtFromAnEarner(cur realm, t *testing.T) {122	reset(cur)123	id := earn(cur, t, alice, 10)124125	testing.SetRealm(testing.NewUserRealm(alice))126	Transfer(cross(cur), bob, 10)127	uassert.Equal(t, int64(0), BalanceOf(alice))128	uassert.Equal(t, int64(10), BalanceOf(bob))129130	testing.SetRealm(testing.NewUserRealm(bob))131	Boost(cross(cur), id)132	uassert.True(t, PinnedUntil(id) > 0, "bob pinned alice's thread")133	uassert.Equal(t, int64(0), TotalSupply())134}135136func TestPostRefusals(cur realm, t *testing.T) {137	reset(cur)138	testing.SetRealm(testing.NewUserRealm(alice))139	uassert.AbortsContains(t, cur, "not a page key", func() {140		Post(cross(cur), "nopath", "body")141	})142	testing.SetRealm(testing.NewUserRealm(alice))143	uassert.AbortsContains(t, cur, "body is empty", func() {144		Post(cross(cur), homePage, "   ")145	})146	testing.SetRealm(testing.NewUserRealm(alice))147	uassert.AbortsContains(t, cur, "no such thread", func() {148		Reply(cross(cur), 99, "into the void")149	})150	uassert.Equal(t, 0, Total())151}152153func TestCountsAreScopedToTheirPage(cur realm, t *testing.T) {154	reset(cur)155	testing.SetRealm(testing.NewUserRealm(alice))156	Post(cross(cur), homePage, "on home")157	testing.SetRealm(testing.NewUserRealm(alice))158	Post(cross(cur), "gno.land/r/moul/blog", "on the blog")159160	uassert.Equal(t, 1, Count(homePage))161	uassert.Equal(t, 0, Count("gno.land/r/nobody/here"))162	uassert.Equal(t, 2, Total())163	uassert.Equal(t, 2, Pages())164}165166// RenderBlockFor is the embed, and it escapes what it shows: a raw pipe in a167// body would otherwise open a column in the host realm's page.168func TestRenderBlockForEscapesTheBody(cur realm, t *testing.T) {169	reset(cur)170	testing.SetRealm(testing.NewUserRealm(alice))171	Post(cross(cur), homePage, "a | pipe")172173	got := RenderBlockFor(homePage)174	uassert.True(t, strings.Contains(got, "func=Post"), "the block offers the transaction: "+got)175	uassert.True(t, strings.Contains(got, `| a \| pipe |`),176		"the body cell escapes the pipe rather than opening a column: "+got)177	uassert.False(t, strings.Contains(got, "| a | pipe"), "no raw pipe survives: "+got)178	uassert.True(t, strings.Contains(RenderBlockFor("gno.land/r/nobody/here"), "No discussion yet."))179}180181// TestRender uses uassert rather than an Example because every view contains182// consecutive blank lines, which gno collapses inside an // Output: block.183func TestRender(cur realm, t *testing.T) {184	reset(cur)185	testing.SetRealm(testing.NewUserRealm(alice))186	id := Post(cross(cur), homePage, "a thread about [links](x)")187	testing.SetRealm(testing.NewUserRealm(bob))188	Reply(cross(cur), id, "a reply with a | pipe")189190	index := Render("")191	uassert.True(t, strings.Contains(index, "| 1 | 1 | 1 |"), "threads, pages, supply: "+index)192	uassert.True(t, strings.Contains(index, tokenPolicy.Sink), "the index declares the sink")193	uassert.True(t, strings.Contains(index, `import "`+realmPath+`"`), "the index is the embed doc")194195	page := Render("page/" + homePage)196	uassert.True(t, strings.Contains(page, "/r/moul/home"), "it links the page itself: "+page)197	uassert.True(t, strings.Contains(page, "func=Post"))198199	sid := strconv.FormatInt(id, 10)200	thread := Render("thread/" + sid)201	uassert.True(t, strings.Contains(thread, "# Thread #"+sid))202	uassert.True(t, strings.Contains(thread, "a thread about \\[links\\]\\(x\\)"),203		"the body is escaped where it is shown: "+thread)204	uassert.True(t, strings.Contains(thread, "a reply with a \\| pipe"),205		"a reply cell cannot open a column: "+thread)206	uassert.True(t, strings.Contains(thread, "func=Boost"))207208	uassert.Equal(t, "# Threads\nNo thread #4242", Render("thread/4242"))209	uassert.Equal(t, "# Threads\nNot a thread id: nope", Render("thread/nope"))210	uassert.Equal(t, "# Threads\nNot a page key: nope", Render("page/nope"))211	uassert.True(t, strings.HasPrefix(Render("elsewhere"), "# Not found"))212}213214func TestTokenIsDiscoverableWithoutImportingThisRealm(t *testing.T) {215	uassert.True(t, strings.HasSuffix(TokenKey(), ".THREAD"), TokenKey())216	uassert.True(t, strings.Contains(TokenKey(), realmPath), TokenKey())217}218

Functions

  • Allowance(owner string, spender string) int64

  • Approve(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}, spender string, amount int64)

  • BalanceOf(owner string) int64

  • Boost(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}, threadID int64)

  • Count(page string) int

  • Pages() int

  • PinnedUntil(threadID int64) int64

  • Post(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}, page string, body string) int64

  • Render(path string) string

  • RenderBlock() string

  • RenderBlockFor(page string) string

  • Repliers(threadID int64) int

  • Reply(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}, threadID int64, body string)

  • TokenKey() string

  • Total() int

  • TotalSupply() int64

  • Transfer(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}, to string, amount int64)

  • TransferFrom(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}, from string, to string, amount int64)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

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.