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/p/moul/x/social/curated/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / social / curated
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/social/curated/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • curated.gnogno
  • curated_test.gnogno
curated_test.gnogno
1package curated23import (4	"strings"5	"testing"67	"gno.land/p/nt/testutils/v0"8	"gno.land/p/nt/uassert/v0"9	"gno.land/p/nt/urequire/v0"10)1112const (13	deposit = int64(1000)14	window  = int64(100)15)1617var (18	alice = testutils.TestAddress("alice")19	bob   = testutils.TestAddress("bob")20	carol = testutils.TestAddress("carol")21	dave  = testutils.TestAddress("dave")22)2324// list returns a registry holding one entry owned by alice, listed at height25// 100, which is the starting point of most cases below.26func list(t *testing.T) *Registry {27	t.Helper()28	r := New(deposit, window)29	urequire.NoError(t, r.Apply("gnoswap", "https://gnoswap.io", "an AMM", alice, deposit, 100))30	return r31}3233func TestValidKey(t *testing.T) {34	tests := []struct {35		key  string36		want bool37	}{38		{"gnoswap", true},39		{"a", true},40		{"0", true},41		{"gno-swap_v2.1", true},42		{strings.Repeat("x", MaxKeyLen), true},43		{"", false},44		{strings.Repeat("x", MaxKeyLen+1), false},45		{"GnoSwap", false},46		{"gno swap", false},47		{"gno/swap", false},48		{"gno:swap", false},49		{".", false},50		{"..", false},51		{"-leading", false},52		{"_leading", false},53		{"émoji", false},54	}55	for _, tt := range tests {56		uassert.Equal(t, tt.want, ValidKey(tt.key), tt.key)57	}58}5960func TestValidURL(t *testing.T) {61	tests := []struct {62		name string63		url  string64		want bool65	}{66		{"https", "https://gnoswap.io/pools", true},67		{"an on-chain path, which has no scheme to carry", "/r/moul/home", true},68		{"query and fragment", "https://x.io/a?b=c#d", true},69		{"a pipe is escaped at render time, not refused", "https://x.io/a|b", true},70		{"empty", "", false},71		{"too long", "https://x.io/" + strings.Repeat("x", MaxURLLen), false},72		{"a space", "https://x.io/a b", false},73		{"a newline", "https://x.io/a\nb", false},74		{"a control character", "https://x.io/a\x01b", false},75	}76	for _, tt := range tests {77		uassert.Equal(t, tt.want, ValidURL(tt.url), tt.name)78	}79}8081func TestValidDescription(t *testing.T) {82	tests := []struct {83		name string84		desc string85		want bool86	}{87		{"plain", "an AMM on gno.land", true},88		{"markdown is allowed and escaped later", "[x](y) | z", true},89		{"empty", "", false},90		{"blank", "   ", false},91		{"too long", strings.Repeat("x", MaxDescLen+1), false},92		{"a newline would break its own table cell", "a\nb", false},93		{"a tab, same reason", "a\tb", false},94	}95	for _, tt := range tests {96		uassert.Equal(t, tt.want, ValidDescription(tt.desc), tt.name)97	}98}99100func TestApplyRefusesWhatItCannotStore(t *testing.T) {101	tests := []struct {102		name string103		key  string104		url  string105		desc string106		paid int64107		want error108	}{109		{"bad key", "Gnoswap", "https://x.io", "an AMM", deposit, ErrBadKey},110		{"bad url", "gnoswap", "", "an AMM", deposit, ErrBadURL},111		{"bad description", "gnoswap", "https://x.io", " ", deposit, ErrBadDescription},112		{"underpaid", "gnoswap", "https://x.io", "an AMM", deposit - 1, ErrWrongDeposit},113		{"overpaid", "gnoswap", "https://x.io", "an AMM", deposit + 1, ErrWrongDeposit},114		{"nothing attached", "gnoswap", "https://x.io", "an AMM", 0, ErrWrongDeposit},115	}116	for _, tt := range tests {117		r := New(deposit, window)118		err := r.Apply(tt.key, tt.url, tt.desc, alice, tt.paid, 100)119		uassert.ErrorIs(t, err, tt.want, tt.name)120		uassert.Equal(t, 0, r.Count(), tt.name+": nothing was listed")121		uassert.Equal(t, int64(0), r.Locked(), tt.name+": nothing was locked")122	}123}124125func TestApplyListsImmediately(t *testing.T) {126	r := list(t)127128	e, ok := r.Get("gnoswap")129	urequire.True(t, ok, "the entry exists")130	uassert.Equal(t, "https://gnoswap.io", e.URL)131	uassert.Equal(t, "an AMM", e.Description)132	uassert.Equal(t, alice.String(), e.Owner.String())133	uassert.Equal(t, deposit, e.Deposit)134	uassert.Equal(t, int64(100), e.At)135	uassert.Equal(t, "listed", e.State.String())136137	uassert.True(t, r.IsListed("gnoswap"))138	uassert.False(t, r.IsListed("nothing"))139	uassert.Equal(t, 1, r.Count())140	uassert.Equal(t, 1, r.Records())141	uassert.Equal(t, deposit, r.Locked())142	uassert.Equal(t, int64(0), r.Owed())143}144145// A key is unique while it is on the list, and free again once it is off it: a146// challenge removes an entry, it does not burn the name.147func TestAKeyIsTakenUntilItIsRemoved(t *testing.T) {148	r := list(t)149150	uassert.ErrorIs(t, r.Apply("gnoswap", "https://other.io", "mine now", bob, deposit, 101), ErrTaken)151	urequire.NoError(t, r.Unlist("gnoswap", alice))152	uassert.False(t, r.IsListed("gnoswap"))153154	urequire.NoError(t, r.Apply("gnoswap", "https://other.io", "mine now", bob, deposit, 102))155	e, _ := r.Get("gnoswap")156	uassert.Equal(t, bob.String(), e.Owner.String())157	uassert.Equal(t, int64(102), e.At, "a re-application is a fresh entry")158	uassert.Equal(t, 1, r.Count())159	uassert.Equal(t, 1, r.Records(), "and it reuses its place in the listing order")160}161162func TestListedIsOldestFirstAndSkipsTheRemoved(t *testing.T) {163	r := New(deposit, window)164	urequire.NoError(t, r.Apply("one", "https://1.io", "first", alice, deposit, 100))165	urequire.NoError(t, r.Apply("two", "https://2.io", "second", alice, deposit, 101))166	urequire.NoError(t, r.Apply("three", "https://3.io", "third", alice, deposit, 102))167168	got := r.Listed()169	urequire.Equal(t, 3, len(got))170	uassert.Equal(t, "one", got[0].Key)171	uassert.Equal(t, "two", got[1].Key)172	uassert.Equal(t, "three", got[2].Key)173174	urequire.NoError(t, r.Unlist("two", alice))175	got = r.Listed()176	urequire.Equal(t, 2, len(got))177	uassert.Equal(t, "one", got[0].Key)178	uassert.Equal(t, "three", got[1].Key)179	uassert.Equal(t, 0, len(r.Challenged()))180}181182func TestChallengeRefusals(t *testing.T) {183	tests := []struct {184		name string185		key  string186		who  address187		bond int64188		want error189	}{190		{"no such entry", "nothing", bob, deposit, ErrNoEntry},191		{"the bond must match", "gnoswap", bob, deposit - 1, ErrWrongBond},192		{"and may not exceed it either", "gnoswap", bob, deposit + 1, ErrWrongBond},193		{"an owner cannot shield their own entry", "gnoswap", alice, deposit, ErrSelfChallenge},194	}195	for _, tt := range tests {196		r := list(t)197		uassert.ErrorIs(t, r.Challenge(tt.key, tt.who, tt.bond, 200), tt.want, tt.name)198		uassert.Equal(t, deposit, r.Locked(), tt.name+": no bond was taken")199		_, open := r.ChallengeOf("gnoswap")200		uassert.False(t, open, tt.name+": no challenge was opened")201	}202}203204func TestChallengeOpensOnceAndSetsTheDeadline(t *testing.T) {205	r := list(t)206207	bond, ok := r.BondFor("gnoswap")208	urequire.True(t, ok, "it is challengeable")209	uassert.Equal(t, deposit, bond, "the bond matches that entry's own deposit")210211	urequire.NoError(t, r.Challenge("gnoswap", bob, bond, 200))212	c, open := r.ChallengeOf("gnoswap")213	urequire.True(t, open, "the challenge is readable")214	uassert.Equal(t, bob.String(), c.Challenger.String())215	uassert.Equal(t, int64(200+window), c.Deadline)216	uassert.True(t, c.Open(200+window-1))217	uassert.False(t, c.Open(200+window), "the deadline is exclusive")218	uassert.Equal(t, "challenged", mustGet(t, r, "gnoswap").State.String())219	uassert.True(t, r.IsListed("gnoswap"), "a challenge is an objection, not a verdict")220	uassert.Equal(t, 2*deposit, r.Locked(), "the deposit and the bond")221222	// A second challenge would be a second bond on one entry.223	uassert.ErrorIs(t, r.Challenge("gnoswap", carol, deposit, 201), ErrChallenged)224	_, ok = r.BondFor("gnoswap")225	uassert.False(t, ok, "and BondFor says so before any money is read")226}227228func TestVoteIsOneAddressOneVoteWhileTheChallengeIsOpen(t *testing.T) {229	r := list(t)230	uassert.ErrorIs(t, r.Vote("gnoswap", carol, true, 200), ErrNoChallenge)231	uassert.ErrorIs(t, r.Vote("nothing", carol, true, 200), ErrNoEntry)232233	urequire.NoError(t, r.Challenge("gnoswap", bob, deposit, 200))234	urequire.NoError(t, r.Vote("gnoswap", carol, true, 210))235	uassert.ErrorIs(t, r.Vote("gnoswap", carol, false, 211), ErrAlreadyVoted)236	uassert.ErrorIs(t, r.Vote("gnoswap", carol, true, 212), ErrAlreadyVoted)237	urequire.NoError(t, r.Vote("gnoswap", dave, false, 213))238239	c, _ := r.ChallengeOf("gnoswap")240	uassert.Equal(t, int64(1), c.Keep)241	uassert.Equal(t, int64(1), c.Remove)242	uassert.Equal(t, 2, c.Voters())243	uassert.True(t, c.HasVoted(carol))244	uassert.False(t, c.HasVoted(alice))245246	// Past the deadline the poll is shut, and so is the deadline itself.247	uassert.ErrorIs(t, r.Vote("gnoswap", alice, true, 200+window), ErrVotingClosed)248	uassert.Equal(t, 2, c.Voters())249}250251// The outcome table: who wins, and what the loser pays. A tie keeps the entry.252func TestResolvePaysTheWinnerAndTiesKeep(t *testing.T) {253	tests := []struct {254		name       string255		keep       []address256		remove     []address257		wantKept   bool258		wantWinner address259		wantAmount int64260	}{261		{"nobody voted, the incumbent keeps it", nil, nil, true, alice, deposit},262		{"a tie keeps it", []address{carol}, []address{dave}, true, alice, deposit},263		{"a majority to keep", []address{carol, dave}, nil, true, alice, deposit},264		{"a majority to remove", nil, []address{carol, dave}, false, bob, 2 * deposit},265	}266	for _, tt := range tests {267		r := list(t)268		urequire.NoError(t, r.Challenge("gnoswap", bob, deposit, 200))269		for _, who := range tt.keep {270			urequire.NoError(t, r.Vote("gnoswap", who, true, 210))271		}272		for _, who := range tt.remove {273			urequire.NoError(t, r.Vote("gnoswap", who, false, 210))274		}275276		out, err := r.Resolve("gnoswap", 200+window)277		urequire.NoError(t, err, tt.name)278		uassert.Equal(t, tt.wantKept, out.Kept, tt.name)279		uassert.Equal(t, tt.wantWinner.String(), out.Winner.String(), tt.name)280		uassert.Equal(t, tt.wantAmount, out.Amount, tt.name)281		uassert.Equal(t, tt.wantAmount, r.CreditOf(tt.wantWinner), tt.name+": credited, not sent")282		uassert.Equal(t, tt.wantKept, r.IsListed("gnoswap"), tt.name)283284		// Whatever happened, every ugnot is either still backing the entry or285		// assigned to somebody.286		uassert.Equal(t, 2*deposit, r.Locked()+r.Owed(), tt.name+": the books balance")287288		// The challenge is gone either way, so nothing resolves twice.289		_, open := r.ChallengeOf("gnoswap")290		uassert.False(t, open, tt.name)291		_, err = r.Resolve("gnoswap", 200+window)292		uassert.ErrorIs(t, err, ErrNoChallenge, tt.name)293	}294}295296func TestResolveRefusesBeforeTheDeadline(t *testing.T) {297	r := list(t)298	_, err := r.Resolve("gnoswap", 200)299	uassert.ErrorIs(t, err, ErrNoChallenge, "an unchallenged entry has nothing to resolve")300	_, err = r.Resolve("nothing", 200)301	uassert.ErrorIs(t, err, ErrNoEntry)302303	urequire.NoError(t, r.Challenge("gnoswap", bob, deposit, 200))304	_, err = r.Resolve("gnoswap", 200+window-1)305	uassert.ErrorIs(t, err, ErrTooEarly)306	uassert.Equal(t, int64(0), r.Owed(), "nothing was paid out early")307	uassert.Equal(t, "challenged", mustGet(t, r, "gnoswap").State.String())308}309310// Losing a challenge frees the key, and the deposit that backed it has already311// gone to the challenger, so re-listing it costs a fresh deposit.312func TestAnEntryThatLosesIsRemovedAndItsDepositIsGone(t *testing.T) {313	r := list(t)314	urequire.NoError(t, r.Challenge("gnoswap", bob, deposit, 200))315	urequire.NoError(t, r.Vote("gnoswap", carol, false, 210))316	_, err := r.Resolve("gnoswap", 200+window)317	urequire.NoError(t, err)318319	uassert.Equal(t, "removed", mustGet(t, r, "gnoswap").State.String())320	uassert.Equal(t, 0, r.Count())321	uassert.Equal(t, int64(0), r.Locked())322	uassert.Equal(t, 2*deposit, r.Owed())323	uassert.Equal(t, int64(0), r.CreditOf(alice), "the owner lost the deposit")324	uassert.ErrorIs(t, r.Unlist("gnoswap", alice), ErrNoEntry, "there is nothing left to take down")325}326327func TestUnlistIsTheOwnersAndNotWhileChallenged(t *testing.T) {328	r := list(t)329	uassert.ErrorIs(t, r.Unlist("nothing", alice), ErrNoEntry)330	uassert.ErrorIs(t, r.Unlist("gnoswap", bob), ErrNotOwner)331332	urequire.NoError(t, r.Challenge("gnoswap", bob, deposit, 200))333	uassert.ErrorIs(t, r.Unlist("gnoswap", alice), ErrChallenged,334		"an owner who could walk away mid-challenge would be risking nothing")335	uassert.Equal(t, int64(0), r.CreditOf(alice))336337	// Once the challenge is behind it, the entry is the owner's to take down.338	urequire.NoError(t, r.Vote("gnoswap", carol, true, 210))339	_, err := r.Resolve("gnoswap", 200+window)340	urequire.NoError(t, err)341	urequire.NoError(t, r.Unlist("gnoswap", alice))342	uassert.Equal(t, 2*deposit, r.CreditOf(alice), "the bond she won, plus her own deposit back")343	uassert.Equal(t, int64(0), r.Locked())344}345346func TestWithdrawZeroesTheCreditBeforeItIsPaid(t *testing.T) {347	r := list(t)348	urequire.NoError(t, r.Unlist("gnoswap", alice))349	uassert.Equal(t, deposit, r.Owed())350351	_, err := r.Withdraw(bob)352	uassert.ErrorIs(t, err, ErrNothingOwed, "bob is owed nothing")353354	got, err := r.Withdraw(alice)355	urequire.NoError(t, err)356	uassert.Equal(t, deposit, got)357	uassert.Equal(t, int64(0), r.CreditOf(alice))358	uassert.Equal(t, int64(0), r.Owed())359360	// A second call, which is what a reentrant one would be, finds nothing.361	_, err = r.Withdraw(alice)362	uassert.ErrorIs(t, err, ErrNothingOwed)363}364365func TestCreditsAccumulateAcrossEntries(t *testing.T) {366	r := list(t)367	urequire.NoError(t, r.Apply("gnoscan", "https://gnoscan.io", "an explorer", alice, deposit, 101))368	urequire.NoError(t, r.Unlist("gnoswap", alice))369	urequire.NoError(t, r.Unlist("gnoscan", alice))370371	uassert.Equal(t, 2*deposit, r.CreditOf(alice), "crediting twice owes the sum")372	got, err := r.Withdraw(alice)373	urequire.NoError(t, err)374	uassert.Equal(t, 2*deposit, got, "and one withdrawal collects both")375}376377// Everything the registry ever took in is either locked behind something live378// or owed to somebody, at every step of a full round.379func TestTheBooksBalanceAtEveryStep(t *testing.T) {380	r := New(deposit, window)381	paidIn := int64(0)382383	check := func(step string) {384		t.Helper()385		uassert.Equal(t, paidIn, r.Locked()+r.Owed(), step)386	}387	check("empty")388389	urequire.NoError(t, r.Apply("one", "https://1.io", "first", alice, deposit, 100))390	paidIn += deposit391	check("after a listing")392393	urequire.NoError(t, r.Challenge("one", bob, deposit, 200))394	paidIn += deposit395	check("after a challenge")396397	urequire.NoError(t, r.Vote("one", carol, false, 210))398	check("after a vote")399400	_, err := r.Resolve("one", 200+window)401	urequire.NoError(t, err)402	check("after a resolution")403404	_, err = r.Withdraw(bob)405	urequire.NoError(t, err)406	paidIn -= 2 * deposit407	check("after the winner collected")408	uassert.Equal(t, int64(0), r.Locked()+r.Owed(), "and the realm owes nothing")409}410411func mustGet(t *testing.T, r *Registry, key string) *Entry {412	t.Helper()413	e, ok := r.Get(key)414	urequire.True(t, ok, "entry "+key+" exists")415	return e416}417

Functions

not supported for pure packages by the node (vm/qfuncs)

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