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