moultest_test.gno
7.81 Kb · 207 lines
1package moultest
2
3import (
4 "chain"
5 "strings"
6 "testing"
7
8 "gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10)
11
12// testing.SetRealm governs only the crossing calls made from the frame that
13// called it, so every account switch below is written inline in the test body
14// rather than in a helper.
15
16func TestDenom(t *testing.T) {
17 // The denom is what a wallet, an explorer and a -send flag all type, and it
18 // is immutable once the first coin exists. Pin the literal: deriving it from
19 // Path would restate the code rather than check it.
20 uassert.Equal(t, "/gno.land/r/moul/x/moultest/v0:moultest", Denom)
21}
22
23func TestClaimIssuesRealCoins(cur realm, t *testing.T) {
24 alice := testutils.TestAddress("mt-alice")
25 testing.SetRealm(testing.NewUserRealm(alice))
26
27 _, ever := NextClaim(alice)
28 uassert.False(t, ever, "no claim recorded before the first one")
29 uassert.Equal(t, int64(0), BalanceOf(alice))
30
31 before := Circulating()
32 got := Claim(cross(cur))
33
34 uassert.Equal(t, ClaimAmount, got)
35 // The balance comes from the bank, not from realm storage: nothing in this
36 // realm records who holds what.
37 uassert.Equal(t, ClaimAmount, BalanceOf(alice))
38 uassert.Equal(t, before+ClaimAmount, Circulating())
39 uassert.Equal(t, ClaimAmount, ClaimedBy(alice))
40
41 next, ever := NextClaim(alice)
42 uassert.True(t, ever, "a cooldown is recorded")
43 uassert.Equal(t, ClaimEvery, next-123, "first test block is 123, so the cooldown lands there")
44}
45
46func TestClaimCooldown(cur realm, t *testing.T) {
47 bob := testutils.TestAddress("mt-bob")
48 testing.SetRealm(testing.NewUserRealm(bob))
49
50 Claim(cross(cur))
51 uassert.AbortsContains(t, cur, "too soon", func() { Claim(cross(cur)) })
52
53 testing.SkipHeights(ClaimEvery - 1)
54 uassert.AbortsContains(t, cur, "too soon", func() { Claim(cross(cur)) })
55
56 testing.SkipHeights(1)
57 Claim(cross(cur))
58 uassert.Equal(t, 2*ClaimAmount, BalanceOf(bob))
59}
60
61func TestAccountCap(cur realm, t *testing.T) {
62 carol := testutils.TestAddress("mt-carol")
63 testing.SetRealm(testing.NewUserRealm(carol))
64
65 for ClaimedBy(carol)+ClaimAmount <= MaxPerAccount {
66 Claim(cross(cur))
67 testing.SkipHeights(ClaimEvery)
68 }
69 uassert.Equal(t, MaxPerAccount, ClaimedBy(carol))
70 uassert.AbortsContains(t, cur, "account cap reached", func() { Claim(cross(cur)) })
71
72 // Waiting does not help: the cooldown is not what stopped it.
73 testing.SkipHeights(ClaimEvery * 10)
74 uassert.AbortsContains(t, cur, "account cap reached", func() { Claim(cross(cur)) })
75}
76
77func TestBurnIsScopedToTheCaller(cur realm, t *testing.T) {
78 dave := testutils.TestAddress("mt-dave")
79 erin := testutils.TestAddress("mt-erin")
80
81 testing.SetRealm(testing.NewUserRealm(erin))
82 Claim(cross(cur))
83 erinBefore := BalanceOf(erin)
84
85 testing.SetRealm(testing.NewUserRealm(dave))
86 Claim(cross(cur))
87
88 issuedBefore, burnedBefore, circBefore := Issued(), Burned(), Circulating()
89 claimedBefore := ClaimedBy(dave)
90
91 Burn(cross(cur), 400)
92
93 uassert.Equal(t, ClaimAmount-400, BalanceOf(dave))
94 uassert.Equal(t, circBefore-400, Circulating(), "burning destroys the coin, it is not moved")
95 uassert.Equal(t, burnedBefore+400, Burned())
96 // Issuance is monotonic on purpose: if burning gave cap headroom back,
97 // claim-burn-claim would be an unlimited faucet.
98 uassert.Equal(t, issuedBefore, Issued())
99 uassert.Equal(t, claimedBefore, ClaimedBy(dave))
100 // Burn takes no address, so erin cannot be reached from dave's call.
101 uassert.Equal(t, erinBefore, BalanceOf(erin))
102
103 uassert.AbortsContains(t, cur, "cannot burn", func() { Burn(cross(cur), 1_000_000) })
104 uassert.AbortsContains(t, cur, "positive amount", func() { Burn(cross(cur), 0) })
105 uassert.AbortsContains(t, cur, "positive amount", func() { Burn(cross(cur), -5) })
106}
107
108func TestTipRidesTheEnvelope(cur realm, t *testing.T) {
109 frank := testutils.TestAddress("mt-frank")
110 grace := testutils.TestAddress("mt-grace")
111
112 // On chain the -send envelope is credited to this realm's address before
113 // the function runs. The test harness has no bank-send primitive, so the
114 // credit is issued directly here; SetOriginSend is what the banker checks
115 // against, and it is the half that bounds what Tip may spend.
116 realmAddr := chain.PackageAddress(Path)
117 envelope := chain.NewCoins(chain.NewCoin(Denom, 250))
118 testing.IssueCoins(realmAddr, envelope)
119 // Unrelated coins already sitting at the realm's address. A RealmSend
120 // banker could spend these; the OriginSend one may not, and that is the
121 // property being pinned.
122 testing.IssueCoins(realmAddr, chain.NewCoins(chain.NewCoin(Denom, 5_000)))
123 realmBefore := BalanceOf(realmAddr)
124
125 testing.SetRealm(testing.NewUserRealm(frank))
126 testing.SetOriginSend(envelope)
127 sent := Tip(cross(cur), grace, "nice work")
128
129 uassert.Equal(t, int64(250), sent)
130 uassert.Equal(t, int64(250), BalanceOf(grace), "the tip landed on the recipient")
131 uassert.Equal(t, realmBefore-250, BalanceOf(realmAddr),
132 "exactly the envelope left the realm, and nothing else it was holding")
133 uassert.Equal(t, 1, len(tips))
134 uassert.Equal(t, "nice work", tips[0].note)
135
136 testing.SetOriginSend(nil)
137}
138
139func TestTipRefusals(cur realm, t *testing.T) {
140 heidi := testutils.TestAddress("mt-heidi")
141 ivan := testutils.TestAddress("mt-ivan")
142
143 testing.SetRealm(testing.NewUserRealm(heidi))
144 testing.SetOriginSend(nil)
145 uassert.AbortsContains(t, cur, "attach moultest", func() { Tip(cross(cur), ivan, "hi") })
146
147 envelope := chain.NewCoins(chain.NewCoin(Denom, 10))
148 testing.IssueCoins(chain.PackageAddress(Path), envelope)
149 testing.SetOriginSend(envelope)
150 uassert.AbortsContains(t, cur, "tip somebody else", func() { Tip(cross(cur), heidi, "me") })
151 uassert.AbortsContains(t, cur, "valid address", func() { Tip(cross(cur), "nope", "hi") })
152 uassert.AbortsContains(t, cur, "note is", func() {
153 Tip(cross(cur), ivan, strings.Repeat("x", MaxNote+1))
154 })
155
156 testing.SetOriginSend(nil)
157}
158
159func TestSupplyCap(cur realm, t *testing.T) {
160 // Fill the remaining supply with fresh accounts, each capped at
161 // MaxPerAccount, then check that the next one is refused. Issuance carries
162 // across tests, so the loop starts from whatever is left rather than from
163 // MaxSupply.
164 names := []string{"cap-a", "cap-b", "cap-c", "cap-d", "cap-e", "cap-f",
165 "cap-g", "cap-h", "cap-i", "cap-j", "cap-k", "cap-l"}
166 for _, name := range names {
167 if Issued()+ClaimAmount > MaxSupply {
168 break
169 }
170 who := testutils.TestAddress(name)
171 testing.SetRealm(testing.NewUserRealm(who))
172 for ClaimedBy(who)+ClaimAmount <= MaxPerAccount && Issued()+ClaimAmount <= MaxSupply {
173 Claim(cross(cur))
174 testing.SkipHeights(ClaimEvery)
175 }
176 }
177 uassert.Equal(t, MaxSupply, Issued(), "the accounts above exhaust the supply exactly")
178
179 late := testutils.TestAddress("cap-late")
180 testing.SetRealm(testing.NewUserRealm(late))
181 uassert.AbortsContains(t, cur, "supply cap reached", func() { Claim(cross(cur)) })
182
183 // Burning does not reopen the faucet either: circulating drops, issued does
184 // not, and the cap is on issued.
185 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("cap-a")))
186 Burn(cross(cur), 500)
187 uassert.Equal(t, MaxSupply, Issued())
188 testing.SetRealm(testing.NewUserRealm(late))
189 uassert.AbortsContains(t, cur, "supply cap reached", func() { Claim(cross(cur)) })
190}
191
192func TestRender(t *testing.T) {
193 index := Render("")
194 uassert.True(t, strings.Contains(index, "# moultest"), "heading:\n"+index)
195 uassert.True(t, strings.Contains(index, Denom), "the denom is on the page")
196 uassert.True(t, strings.Contains(index, "| issued, ever |"), "supply table:\n"+index)
197 uassert.True(t, strings.Contains(index, "nice work"), "the tip board is shown:\n"+index)
198
199 frank := testutils.TestAddress("mt-frank")
200 acct := Render(frank.String())
201 uassert.True(t, strings.Contains(acct, frank.String()), "the account section names it:\n"+acct)
202 uassert.True(t, strings.Contains(acct, "claimed for life"), acct)
203
204 // A path that is not an address renders the index without an account block.
205 uassert.False(t, strings.Contains(Render("not-an-address"), "claimed for life"),
206 "a junk path adds no account section")
207}