Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

vesting_test.gno

10.36 Kb · 282 lines
  1package vesting
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"chain/runtime"
  8
  9	"gno.land/p/nt/testutils/v0"
 10	"gno.land/p/nt/uassert/v0"
 11	"gno.land/p/nt/ufmt/v0"
 12)
 13
 14const (
 15	mainnetStart int64 = 1789225200
 16	mainnetEnd   int64 = 1852383600
 17	moul               = address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")
 18)
 19
 20// TestSeededSurvivesInit is the property the seed exists for: this realm is
 21// private = true, so a redeploy re-runs init() and wipes everything else. The
 22// one schedule the page is about has to come back without a transaction.
 23func TestSeededSurvivesInit(t *testing.T) {
 24	o, s, e, delayed, ok := ScheduleOf(moul)
 25	uassert.True(t, ok, "moul's schedule is missing after init")
 26	uassert.Equal(t, int64(106560000000), o)
 27	uassert.Equal(t, mainnetStart, s)
 28	uassert.Equal(t, mainnetEnd, e)
 29	uassert.False(t, delayed)
 30}
 31
 32// TestDeclareWritesOnlyYourOwnRow is the entire trust model. A caller cannot
 33// name an address, so it cannot describe one but its own.
 34func TestDeclareWritesOnlyYourOwnRow(cur realm, t *testing.T) {
 35	alice := testutils.TestAddress("alice")
 36	bob := testutils.TestAddress("bob")
 37
 38	testing.SetRealm(testing.NewUserRealm(alice))
 39	Declare(cross(cur), 500, 100, 200, false)
 40
 41	o, s, e, delayed, ok := ScheduleOf(alice)
 42	uassert.True(t, ok)
 43	uassert.Equal(t, int64(500), o)
 44	uassert.Equal(t, int64(100), s)
 45	uassert.Equal(t, int64(200), e)
 46	uassert.False(t, delayed)
 47
 48	// Nothing alice did touched bob.
 49	_, _, _, _, ok = ScheduleOf(bob)
 50	uassert.False(t, ok, "declaring for alice wrote a row for bob")
 51}
 52
 53func TestDeclareReplacesAndForgetRemoves(cur realm, t *testing.T) {
 54	carol := testutils.TestAddress("carol")
 55
 56	testing.SetRealm(testing.NewUserRealm(carol))
 57	Declare(cross(cur), 500, 100, 200, false)
 58	testing.SetRealm(testing.NewUserRealm(carol))
 59	Declare(cross(cur), 900, 10, 20, true)
 60
 61	o, s, e, delayed, ok := ScheduleOf(carol)
 62	uassert.True(t, ok)
 63	uassert.Equal(t, int64(900), o)
 64	uassert.Equal(t, int64(10), s)
 65	uassert.Equal(t, int64(20), e)
 66	uassert.True(t, delayed, "the delayed flag did not survive")
 67
 68	testing.SetRealm(testing.NewUserRealm(carol))
 69	Forget(cross(cur))
 70	_, _, _, _, ok = ScheduleOf(carol)
 71	uassert.False(t, ok, "Forget left the row behind")
 72}
 73
 74func TestForgetWithoutADeclarationAborts(cur realm, t *testing.T) {
 75	dave := testutils.TestAddress("dave")
 76	testing.SetRealm(testing.NewUserRealm(dave))
 77	uassert.AbortsContains(t, cur, "nothing declared", func() { Forget(cross(cur)) })
 78}
 79
 80// TestSeededRowIsNotWritable: the seeds are source, so a signer who happens to
 81// hold a seeded address still cannot rewrite it from a transaction. Otherwise
 82// a redeploy would silently revert whatever they set.
 83func TestSeededRowIsNotWritable(cur realm, t *testing.T) {
 84	testing.SetRealm(testing.NewUserRealm(moul))
 85	uassert.AbortsContains(t, cur, "seeded in the source", func() {
 86		Declare(cross(cur), 1, 2, 3, false)
 87	})
 88	testing.SetRealm(testing.NewUserRealm(moul))
 89	uassert.AbortsContains(t, cur, "seeded in the source", func() { Forget(cross(cur)) })
 90}
 91
 92func TestDeclareRejectsAnImpossibleSchedule(cur realm, t *testing.T) {
 93	erin := testutils.TestAddress("erin")
 94	testing.SetRealm(testing.NewUserRealm(erin))
 95	uassert.AbortsContains(t, cur, "start time must be before end time", func() {
 96		Declare(cross(cur), 100, 200, 100, false)
 97	})
 98	testing.SetRealm(testing.NewUserRealm(erin))
 99	uassert.AbortsContains(t, cur, "cannot be negative", func() {
100		Declare(cross(cur), -1, 0, 100, false)
101	})
102}
103
104// TestDeclareZeroMeansNoSchedule lets an address say "nothing of mine is
105// locked", which is a real and useful claim.
106func TestDeclareZeroMeansNoSchedule(cur realm, t *testing.T) {
107	frank := testutils.TestAddress("frank")
108	testing.SetRealm(testing.NewUserRealm(frank))
109	Declare(cross(cur), 0, 0, 0, false)
110
111	o, _, _, _, ok := ScheduleOf(frank)
112	uassert.True(t, ok)
113	uassert.Equal(t, int64(0), o)
114
115	spend, known := SpendableOf(frank)
116	uassert.True(t, known)
117	uassert.Equal(t, balanceOf(frank), spend)
118}
119
120// TestUnknownAddressReportsItsBalanceAndSaysSo: the balance is real, the
121// spendable figure is not, and the caller is told which is which.
122func TestUnknownAddressReportsItsBalanceAndSaysSo(t *testing.T) {
123	stranger := testutils.TestAddress("stranger")
124
125	spend, ok := SpendableOf(stranger)
126	uassert.False(t, ok, "an unknown address must not claim a known schedule")
127	uassert.Equal(t, balanceOf(stranger), spend)
128
129	locked, ok := LockedOf(stranger)
130	uassert.False(t, ok)
131	uassert.Equal(t, int64(0), locked)
132}
133
134func TestCountTracksTheRegistry(cur realm, t *testing.T) {
135	before := Count()
136	grace := testutils.TestAddress("grace")
137	testing.SetRealm(testing.NewUserRealm(grace))
138	Declare(cross(cur), 10, 1, 2, false)
139	uassert.Equal(t, before+1, Count())
140	testing.SetRealm(testing.NewUserRealm(grace))
141	Forget(cross(cur))
142	uassert.Equal(t, before, Count())
143}
144
145// TestLockedIsCappedByTheBalance: an account that spent while its coins were
146// free can owe the schedule more than it now holds. The honest answer is that
147// nothing moves, never a negative spendable.
148func TestLockedIsCappedByTheBalance(cur realm, t *testing.T) {
149	poor := testutils.TestAddress("poor")
150	testing.SetRealm(testing.NewUserRealm(poor))
151	Declare(cross(cur), 1000000000000, mainnetStart, mainnetEnd, false)
152
153	locked, ok := LockedOf(poor)
154	uassert.True(t, ok)
155	uassert.Equal(t, balanceOf(poor), locked, "locked must not exceed the balance")
156
157	spend, _ := SpendableOf(poor)
158	uassert.Equal(t, int64(0), spend)
159	uassert.True(t, spend >= 0, "spendable went negative")
160}
161
162func TestIsAddr(t *testing.T) {
163	uassert.True(t, isAddr("g1manfred47kzduec920z88wfr64ylksmdcedlf5"))
164	uassert.False(t, isAddr(""))
165	uassert.False(t, isAddr("g1manfred"))
166	uassert.False(t, isAddr("x1manfred47kzduec920z88wfr64ylksmdcedlf5"))
167	uassert.False(t, isAddr("g1MANFRED47kzduec920z88wfr64ylksmdcedlf5"))
168	uassert.False(t, isAddr("g1manfred47kzduec920z88wfr64ylksmdcedlf5x"))
169}
170
171func TestGnotFormatting(t *testing.T) {
172	uassert.Equal(t, "0.000000 GNOT", gnot(0))
173	uassert.Equal(t, "0.000001 GNOT", gnot(1))
174	uassert.Equal(t, "1.000000 GNOT", gnot(1000000))
175	uassert.Equal(t, "106560.000000 GNOT", gnot(106560000000))
176	uassert.Equal(t, "1.234567 GNOT", gnot(1234567))
177	uassert.Equal(t, "-1.500000 GNOT", gnot(-1500000))
178}
179
180func TestPermilleFormatting(t *testing.T) {
181	uassert.Equal(t, "0.0%", permille(0))
182	uassert.Equal(t, "5.0%", permille(50))
183	uassert.Equal(t, "49.9%", permille(499))
184	uassert.Equal(t, "100.0%", permille(1000))
185}
186
187func TestDurationFormatting(t *testing.T) {
188	uassert.Equal(t, "complete", duration(0))
189	uassert.Equal(t, "complete", duration(-1))
190	uassert.Equal(t, "0h", duration(60))
191	uassert.Equal(t, "1h", duration(3600))
192	uassert.Equal(t, "1d 0h", duration(86400))
193	uassert.Equal(t, "2d 3h", duration(2*86400+3*3600))
194}
195
196// TestCivilDates pins the date arithmetic against dates that are known exactly,
197// including the two ends of the real mainnet term and a leap day.
198func TestCivilDates(t *testing.T) {
199	uassert.Equal(t, "1970-01-01", tstamp(0))
200	uassert.Equal(t, "2000-02-29", tstamp(951782400))
201	uassert.Equal(t, "2026-09-12", tstamp(mainnetStart))
202	uassert.Equal(t, "2028-09-12", tstamp(mainnetEnd))
203}
204
205// TestRenderShapes walks the three routes the page serves and checks each one
206// names its source, which is the promise the whole realm makes.
207func TestRenderShapes(t *testing.T) {
208	index := Render("")
209	uassert.True(t, strings.Contains(index, "# Vesting"), "index has no title")
210	uassert.True(t, strings.Contains(index, "no realm can read one"),
211		"index does not say the schedule is supplied")
212
213	seeded := Render(moul.String())
214	uassert.True(t, strings.Contains(seeded, "seeded in this realm's source"),
215		"a seeded row does not say where it came from")
216	uassert.True(t, strings.Contains(seeded, "spendable now"))
217
218	query := Render(moul.String() + "?o=1000000&s=100&e=200")
219	uassert.True(t, strings.Contains(query, "supplied in the URL by you"),
220		"a query schedule does not say it was supplied")
221	uassert.True(t, strings.Contains(query, "Nothing was stored."))
222
223	unknown := Render(testutils.TestAddress("nobody").String())
224	uassert.True(t, strings.Contains(unknown, "No schedule on file"))
225	uassert.True(t, strings.Contains(unknown, "gnokey query auth/accounts/"),
226		"the unknown page does not say how to find the schedule")
227
228	bad := Render("not-an-address")
229	uassert.True(t, strings.Contains(bad, "is not a gno.land address"))
230}
231
232// TestRenderEscapesWhatTheCallerTyped: the path is attacker-controlled and a
233// live realm can never be patched, so the escaping is checked before deploy.
234func TestRenderEscapesWhatTheCallerTyped(t *testing.T) {
235	out := Render("[x](/r/evil)")
236	uassert.False(t, strings.Contains(out, "[x](/r/evil)"),
237		"a markdown link typed into the path reached the page unescaped")
238}
239
240// TestRenderQueryRejectsGarbage keeps a typo readable instead of rendering a
241// schedule of zeroes as though it were real.
242func TestRenderQueryRejectsGarbage(t *testing.T) {
243	out := Render(moul.String() + "?o=abc")
244	uassert.True(t, strings.Contains(out, "is not a number"))
245
246	out = Render(moul.String() + "?o=100&s=200&e=100")
247	uassert.True(t, strings.Contains(out, "not a valid schedule"))
248}
249
250// TestChainHeightIsReadable guards the one call that would make Declare
251// non-deterministic if it were wrong.
252func TestChainHeightIsReadable(t *testing.T) {
253	uassert.Equal(t, runtime.ChainHeight(), chainHeight())
254}
255
256// TestIndexIsBounded is the defect this realm shipped with: renderIndex
257// iterated EVERY registry row, and Declare is open to anyone, so the front
258// page grew without limit until it exceeded the query gas ceiling and stopped
259// rendering for everybody.
260//
261// vm/qrender runs on a gas meter. A realm that returns a large string fails
262// with OutOfGasError{Descriptor:"CPUCycles"} rather than answering slowly, and
263// the safe budget is tens of KB. A row is ~120 bytes, so a few hundred
264// declarations was enough. The realm is private = true, so the only repair
265// would have been a redeploy, which wipes every declaration and re-charges the
266// whole storage deposit.
267func TestIndexIsBounded(cur realm, t *testing.T) {
268	reset()
269	for i := 0; i < 400; i++ {
270		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress(ufmt.Sprintf("spam%d", i))))
271		Declare(cross(cur), 1000, 100, 200, false)
272	}
273	uassert.Equal(t, 400, Count(), "the registry should hold every declaration")
274
275	out := Render("")
276	uassert.True(t, len(out) < 12000,
277		ufmt.Sprintf("the index rendered %d bytes for 401 rows; it must page", len(out)))
278
279	// Paging still has to reach them, so page 2 differs from page 1.
280	uassert.True(t, Render("?page=2") != out, "?page=2 rendered the same page as page 1")
281	reset()
282}