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/patron/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / social / patron
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/patron/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • patron.gnogno
  • patron_test.gnogno
patron_test.gnogno
1package patron23import (4	"strings"5	"testing"67	"gno.land/p/moul/kit/store/v0"8	"gno.land/p/nt/testutils/v0"9	"gno.land/p/nt/uassert/v0"10	"gno.land/p/nt/urequire/v0"11)1213var (14	alice = testutils.TestAddress("alice")15	bob   = testutils.TestAddress("bob")16	carol = testutils.TestAddress("carol")17)1819// plan opens one plan with the given price and period and returns its id,20// failing the test rather than returning an error nobody reads.21func plan(t *testing.T, r *Registry, price, period int64) store.ID {22	t.Helper()23	id, err := r.Open(alice, "a plan", "every period, forever", price, period)24	urequire.NoError(t, err)25	return id26}2728// planOf reads a plan back, failing the test when it is not there.29func planOf(t *testing.T, r *Registry, id store.ID) *Plan {30	t.Helper()31	p, ok := r.Get(id)32	urequire.True(t, ok, "plan #"+id.String()+" exists")33	return p34}3536func TestValidTitle(t *testing.T) {37	tests := []struct {38		name  string39		title string40		want  bool41	}{42		{"plain", "monthly support", true},43		{"markdown is allowed and escaped later", "[x](y) | z", true},44		{"at the limit", strings.Repeat("x", MaxTitleLen), true},45		{"empty", "", false},46		{"blank", "   ", false},47		{"too long", strings.Repeat("x", MaxTitleLen+1), false},48		{"a title is one line", "two\nlines", false},49		{"tab is a control character here", "a\tb", false},50		{"delete character", "a\x7fb", false},51	}52	for _, tt := range tests {53		uassert.Equal(t, tt.want, ValidTitle(tt.title), tt.name)54	}55}5657func TestValidDescription(t *testing.T) {58	tests := []struct {59		name string60		desc string61		want bool62	}{63		{"empty is fine, the title may say it all", "", true},64		{"newlines and tabs are content", "a\nb\tc", true},65		{"at the limit", strings.Repeat("x", MaxDescriptionLen), true},66		{"too long", strings.Repeat("x", MaxDescriptionLen+1), false},67		{"control character", "a\x01b", false},68		{"delete character", "a\x7fb", false},69	}70	for _, tt := range tests {71		uassert.Equal(t, tt.want, ValidDescription(tt.desc), tt.name)72	}73}7475func TestOpenRejectsWhatItCannotStore(t *testing.T) {76	tests := []struct {77		name   string78		title  string79		desc   string80		price  int6481		period int6482		want   error83	}{84		{"no title", "", "d", 100, 100, ErrBadTitle},85		{"description too long", "t", strings.Repeat("x", MaxDescriptionLen+1), 100, 100, ErrBadDescription},86		{"a free plan is a tip jar", "t", "d", 0, 100, ErrBadPrice},87		{"a negative price", "t", "d", -1, 100, ErrBadPrice},88		{"no period at all", "t", "d", 100, 0, ErrBadPeriod},89		{"under the floor", "t", "d", 100, MinPeriodBlocks - 1, ErrBadPeriod},90		{"over the ceiling", "t", "d", 100, MaxPeriodBlocks + 1, ErrBadPeriod},91	}92	r := NewRegistry()93	for _, tt := range tests {94		_, err := r.Open(alice, tt.title, tt.desc, tt.price, tt.period)95		uassert.ErrorIs(t, err, tt.want, tt.name)96	}97	uassert.Equal(t, 0, r.Count(), "nothing refused was stored")9899	// The edges themselves are accepted.100	_, err := r.Open(alice, "t", "", MinPricePerPeriod, MinPeriodBlocks)101	uassert.NoError(t, err)102	_, err = r.Open(alice, "t", "", MinPricePerPeriod, MaxPeriodBlocks)103	uassert.NoError(t, err)104	uassert.Equal(t, 2, r.Count())105}106107func TestOnePeriodBuysExactlyOnePeriod(t *testing.T) {108	r := NewRegistry()109	id := plan(t, r, 1000, 100)110111	pay, err := r.Subscribe(id, bob, 1000, 500)112	urequire.NoError(t, err)113114	uassert.Equal(t, int64(1), pay.Periods)115	uassert.Equal(t, int64(1000), pay.Spent)116	uassert.Equal(t, int64(0), pay.Change)117	uassert.Equal(t, int64(600), pay.PaidThrough, "500 + one period of 100 blocks")118	uassert.True(t, pay.NewSupporter)119120	uassert.Equal(t, int64(1000), r.CreditOf(alice), "the creator is credited at payment time")121	uassert.Equal(t, int64(1000), r.EarnedBy(alice))122	uassert.Equal(t, int64(0), r.CreditOf(bob), "an exact payment leaves no change")123	uassert.Equal(t, int64(1000), r.TotalOwed())124}125126func TestThreePeriodsAtOnce(t *testing.T) {127	r := NewRegistry()128	id := plan(t, r, 1000, 100)129130	pay, err := r.Subscribe(id, bob, 3000, 500)131	urequire.NoError(t, err)132133	uassert.Equal(t, int64(3), pay.Periods)134	uassert.Equal(t, int64(3000), pay.Spent)135	uassert.Equal(t, int64(0), pay.Change)136	uassert.Equal(t, int64(800), pay.PaidThrough, "three periods of 100 blocks from 500")137}138139// Renewing before the current period ends adds a whole period on top of what140// is left, rather than restarting the clock and discarding it.141func TestRenewingEarlyExtendsFromPaidThrough(t *testing.T) {142	r := NewRegistry()143	id := plan(t, r, 1000, 100)144145	first, err := r.Subscribe(id, bob, 1000, 500)146	urequire.NoError(t, err)147	urequire.Equal(t, int64(600), first.PaidThrough)148149	// Two blocks before the lapse, with 98 blocks still paid for.150	second, err := r.Subscribe(id, bob, 1000, 502)151	urequire.NoError(t, err)152	uassert.Equal(t, int64(700), second.PaidThrough, "600 + 100, not 502 + 100")153	uassert.False(t, second.NewSupporter, "bob was already a supporter")154	uassert.Equal(t, 1, planOf(t, r, id).SupporterCount(), "renewing is not a second supporter")155}156157// Renewing after a lapse starts from now: the gap was never paid for and158// nothing backdates it.159func TestRenewingAfterLapseStartsFromNow(t *testing.T) {160	r := NewRegistry()161	id := plan(t, r, 1000, 100)162163	_, err := r.Subscribe(id, bob, 1000, 500)164	urequire.NoError(t, err)165166	// 900 is long past the 600 the first period ran to.167	second, err := r.Subscribe(id, bob, 1000, 900)168	urequire.NoError(t, err)169	uassert.Equal(t, int64(1000), second.PaidThrough, "900 + 100, the gap is not backdated")170}171172// Paying exactly at the paid-through height is the boundary between the two173// rules above, and it resolves the same way either way: 600 is not later than174// 600, so the extension runs from 600.175func TestRenewingAtTheBoundary(t *testing.T) {176	r := NewRegistry()177	id := plan(t, r, 1000, 100)178179	_, err := r.Subscribe(id, bob, 1000, 500)180	urequire.NoError(t, err)181	second, err := r.Subscribe(id, bob, 1000, 600)182	urequire.NoError(t, err)183	uassert.Equal(t, int64(700), second.PaidThrough)184}185186func TestAPaymentShortOfOnePeriodIsRefused(t *testing.T) {187	r := NewRegistry()188	id := plan(t, r, 1000, 100)189190	for _, sent := range []int64{0, 1, 999} {191		_, err := r.Subscribe(id, bob, sent, 500)192		uassert.ErrorIs(t, err, ErrShortOfOnePeriod)193	}194	uassert.Equal(t, int64(0), r.CreditOf(alice), "a refused payment credits nobody")195	uassert.Equal(t, int64(0), planOf(t, r, id).PaidThrough(bob))196	uassert.Equal(t, 0, planOf(t, r, id).SupporterCount())197}198199// The remainder under one period is the supporter's, not the realm's: keeping200// it would be a fee nobody agreed to.201func TestChangeIsCreditedBackAndIsWithdrawable(t *testing.T) {202	r := NewRegistry()203	id := plan(t, r, 1000, 100)204205	pay, err := r.Subscribe(id, bob, 2500, 500)206	urequire.NoError(t, err)207208	uassert.Equal(t, int64(2), pay.Periods)209	uassert.Equal(t, int64(2000), pay.Spent)210	uassert.Equal(t, int64(500), pay.Change)211	uassert.Equal(t, int64(700), pay.PaidThrough)212213	uassert.Equal(t, int64(500), r.CreditOf(bob), "the change is the supporter's")214	uassert.Equal(t, int64(2000), r.CreditOf(alice))215	uassert.Equal(t, int64(2500), r.TotalOwed(), "every ugnot that came in is owed to somebody")216	uassert.Equal(t, int64(0), r.EarnedBy(bob), "change is not earnings")217218	got, err := r.Withdraw(bob)219	urequire.NoError(t, err)220	uassert.Equal(t, int64(500), got)221	uassert.Equal(t, int64(0), r.CreditOf(bob))222	uassert.Equal(t, int64(2000), r.TotalOwed())223}224225// Withdraw zeroes the credit before it reports it, so the second call of a226// reentrant pair finds nothing.227func TestWithdrawZeroesBeforeItPays(t *testing.T) {228	r := NewRegistry()229	id := plan(t, r, 1000, 100)230	_, err := r.Subscribe(id, bob, 1000, 500)231	urequire.NoError(t, err)232233	got, err := r.Withdraw(alice)234	urequire.NoError(t, err)235	uassert.Equal(t, int64(1000), got)236237	_, err = r.Withdraw(alice)238	uassert.ErrorIs(t, err, ErrNothingToWithdraw)239240	_, err = r.Withdraw(carol)241	uassert.ErrorIs(t, err, ErrNothingToWithdraw, "an address owed nothing withdraws nothing")242243	uassert.Equal(t, int64(1000), r.EarnedBy(alice), "earnings outlive the withdrawal")244	uassert.Equal(t, int64(0), r.CreditOf(alice))245}246247// IsActive is strict on both sides: active at paidThrough-1, not at248// paidThrough.249func TestIsActiveFlipsAtTheRightHeight(t *testing.T) {250	r := NewRegistry()251	id := plan(t, r, 1000, 100)252	_, err := r.Subscribe(id, bob, 1000, 500)253	urequire.NoError(t, err)254	p := planOf(t, r, id)255256	tests := []struct {257		name string258		now  int64259		want bool260	}{261		{"the block it was bought in", 500, true},262		{"one before the end", 599, true},263		{"the end itself", 600, false},264		{"after", 601, false},265	}266	for _, tt := range tests {267		uassert.Equal(t, tt.want, p.IsActive(bob, tt.now), tt.name)268	}269270	uassert.False(t, p.IsActive(carol, 500), "an address that never paid is never active")271	uassert.Equal(t, int64(600), p.PaidThrough(bob))272	uassert.Equal(t, int64(0), p.PaidThrough(carol))273	uassert.Equal(t, 1, p.ActiveCount(599))274	uassert.Equal(t, 0, p.ActiveCount(600))275}276277func TestCloseStopsNewMoneyAndNotPaidTime(t *testing.T) {278	r := NewRegistry()279	id := plan(t, r, 1000, 100)280	_, err := r.Subscribe(id, bob, 1000, 500)281	urequire.NoError(t, err)282283	uassert.ErrorIs(t, r.Close(id, bob), ErrNotCreator)284	urequire.NoError(t, r.Close(id, alice))285	uassert.ErrorIs(t, r.Close(id, alice), ErrAlreadyClosed)286287	_, err = r.Subscribe(id, carol, 1000, 501)288	uassert.ErrorIs(t, err, ErrPlanClosed)289290	p := planOf(t, r, id)291	uassert.False(t, p.Open)292	uassert.True(t, p.IsActive(bob, 599), "a closed plan does not take back a paid period")293294	uassert.ErrorIs(t, r.Reopen(id, bob), ErrNotCreator)295	urequire.NoError(t, r.Reopen(id, alice))296	uassert.ErrorIs(t, r.Reopen(id, alice), ErrAlreadyOpen)297298	_, err = r.Subscribe(id, carol, 1000, 501)299	uassert.NoError(t, err)300}301302func TestNothingWorksOnAPlanThatIsNotThere(t *testing.T) {303	r := NewRegistry()304	_, err := r.Subscribe(4242, bob, 1000, 500)305	uassert.ErrorIs(t, err, ErrNoPlan)306	uassert.ErrorIs(t, r.Close(4242, alice), ErrNoPlan)307	uassert.ErrorIs(t, r.Reopen(4242, alice), ErrNoPlan)308309	_, ok := r.Get(4242)310	uassert.False(t, ok)311}312313func TestOnePaymentCannotBuyUnboundedTime(t *testing.T) {314	r := NewRegistry()315	id := plan(t, r, 1, MaxPeriodBlocks)316317	_, err := r.Subscribe(id, bob, MaxPeriodsPerPayment+1, 500)318	uassert.ErrorIs(t, err, ErrTooManyPeriods)319	uassert.Equal(t, int64(0), planOf(t, r, id).PaidThrough(bob))320321	// The ceiling itself is accepted, and the product is the one place the322	// naive arithmetic would have wrapped.323	pay, err := r.Subscribe(id, bob, MaxPeriodsPerPayment, 500)324	urequire.NoError(t, err)325	uassert.Equal(t, 500+MaxPeriodsPerPayment*MaxPeriodBlocks, pay.PaidThrough)326}327328func TestSupportersAreDistinctAndInFirstPaymentOrder(t *testing.T) {329	r := NewRegistry()330	id := plan(t, r, 1000, 100)331332	_, err := r.Subscribe(id, carol, 1000, 500)333	urequire.NoError(t, err)334	_, err = r.Subscribe(id, bob, 1000, 501)335	urequire.NoError(t, err)336	_, err = r.Subscribe(id, carol, 1000, 502)337	urequire.NoError(t, err)338339	p := planOf(t, r, id)340	got := p.Supporters()341	urequire.Equal(t, 2, len(got))342	uassert.Equal(t, carol.String(), got[0].String(), "first payment wins the first slot")343	uassert.Equal(t, bob.String(), got[1].String())344	uassert.Equal(t, 2, p.SupporterCount())345346	// The slice handed out is a copy: writing to it cannot reach the plan.347	got[0] = bob348	uassert.Equal(t, carol.String(), p.Supporters()[0].String())349}350351func TestEarningsAccumulateAcrossPlansAndSupporters(t *testing.T) {352	r := NewRegistry()353	first := plan(t, r, 1000, 100)354	second := plan(t, r, 2000, 200)355356	_, err := r.Subscribe(first, bob, 1000, 500)357	urequire.NoError(t, err)358	_, err = r.Subscribe(second, carol, 4000, 500)359	urequire.NoError(t, err)360361	uassert.Equal(t, int64(5000), r.EarnedBy(alice))362	uassert.Equal(t, int64(5000), r.CreditOf(alice))363	uassert.Equal(t, int64(1000), planOf(t, r, first).Received)364	uassert.Equal(t, int64(4000), planOf(t, r, second).Received)365	uassert.Equal(t, 2, r.Count())366}367368func TestListIsNewestFirst(t *testing.T) {369	r := NewRegistry()370	first := plan(t, r, 1000, 100)371	second := plan(t, r, 1000, 100)372	third := plan(t, r, 1000, 100)373374	got := r.List(1, 10)375	urequire.Equal(t, 3, len(got))376	uassert.Equal(t, uint64(third), uint64(got[0].ID))377	uassert.Equal(t, uint64(second), uint64(got[1].ID))378	uassert.Equal(t, uint64(first), uint64(got[2].ID))379	uassert.Equal(t, 2, r.Pages(2))380	uassert.Equal(t, 0, len(r.List(9, 10)))381}382383func TestURLsStripTheChainDomain(t *testing.T) {384	uassert.Equal(t, "/r/moul/x/social/patron/v0", RealmURL("gno.land/r/moul/x/social/patron/v0"))385	uassert.Equal(t, "/r/a/b:plan/7", PlanURL("gno.land/r/a/b", 7))386}387

Functions

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

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