vouch_test.gno
9.71 Kb · 292 lines
1package vouch
2
3import (
4 "strings"
5 "testing"
6
7 "chain"
8 "chain/banker"
9
10 vo "gno.land/p/moul/x/social/vouch/v0"
11 "gno.land/p/nt/testutils/v0"
12 "gno.land/p/nt/uassert/v0"
13)
14
15var (
16 alice = testutils.TestAddress("alice")
17 bob = testutils.TestAddress("bob")
18 carol = testutils.TestAddress("carol")
19
20 realmAddr = chain.PackageAddress(realmPath)
21)
22
23// reset clears realm state. Realm globals live for the whole test binary, so
24// every test that reads a score, a bond or a refund starts here.
25func reset() { graph = vo.NewGraph() }
26
27// fund simulates the coins attached to the next call: the runtime credits the
28// realm's address first, and the crossing function then reads the envelope off
29// the transaction. fund(0) clears it, which is the ordinary unbonded case.
30//
31// testing.SetRealm is call-frame scoped, so the caller still switches account
32// inline in the test body right before crossing.
33func fund(amount int64) {
34 if amount <= 0 {
35 testing.SetOriginSend(chain.Coins{})
36 return
37 }
38 testing.IssueCoins(realmAddr, chain.Coins{{denom, amount}})
39 testing.SetOriginSend(chain.Coins{{denom, amount}})
40}
41
42func balanceOf(addr address) int64 {
43 return banker.NewReadonlyBanker().GetCoins(addr).AmountOf(denom)
44}
45
46// The score counts people, which is the property every caller of IsTrusted
47// depends on: a repeated vouch does not raise it, a revoke lowers it.
48func TestScoreCountsPeopleAndNotVouches(cur realm, t *testing.T) {
49 reset()
50
51 fund(0)
52 testing.SetRealm(testing.NewUserRealm(bob))
53 Vouch(cross(cur), alice, "worked with them for two years")
54 uassert.Equal(t, 1, ScoreOf(alice))
55
56 fund(0)
57 testing.SetRealm(testing.NewUserRealm(bob))
58 Vouch(cross(cur), alice, "still true")
59 uassert.Equal(t, 1, ScoreOf(alice), "one person is one point, however often they say it")
60 uassert.Equal(t, "still true", ReasonFrom(bob, alice), "the reason was updated")
61 uassert.Equal(t, 1, Count())
62
63 fund(0)
64 testing.SetRealm(testing.NewUserRealm(carol))
65 Vouch(cross(cur), alice, "we shipped together")
66 uassert.Equal(t, 2, ScoreOf(alice))
67 uassert.Equal(t, 1, People(), "one address has been vouched for")
68
69 fund(0)
70 testing.SetRealm(testing.NewUserRealm(carol))
71 Revoke(cross(cur), alice)
72 uassert.Equal(t, 1, ScoreOf(alice), "revoking lowers the score")
73 uassert.Equal(t, 1, Count())
74}
75
76func TestVouchRefusals(cur realm, t *testing.T) {
77 reset()
78
79 fund(0)
80 testing.SetRealm(testing.NewUserRealm(alice))
81 uassert.AbortsContains(t, cur, "cannot vouch for itself", func() {
82 Vouch(cross(cur), alice, "I am great")
83 })
84
85 fund(0)
86 testing.SetRealm(testing.NewUserRealm(alice))
87 uassert.AbortsContains(t, cur, "reason is empty", func() {
88 Vouch(cross(cur), bob, " ")
89 })
90
91 fund(0)
92 testing.SetRealm(testing.NewUserRealm(alice))
93 uassert.AbortsContains(t, cur, "reason is empty", func() {
94 Vouch(cross(cur), bob, "one line\nonly")
95 })
96
97 fund(0)
98 testing.SetRealm(testing.NewUserRealm(alice))
99 uassert.AbortsContains(t, cur, "no such vouch", func() {
100 Revoke(cross(cur), bob)
101 })
102
103 fund(0)
104 testing.SetRealm(testing.NewUserRealm(alice))
105 uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
106 Withdraw(cross(cur))
107 })
108
109 uassert.Equal(t, 0, Count())
110}
111
112// The bond follows the vouch in both directions, and the coins only move on
113// Withdraw. Balances are asserted here because the bank does not carry over
114// between test functions.
115func TestTheBondIsLockedUntilTheVouchIsRevoked(cur realm, t *testing.T) {
116 reset()
117 before := balanceOf(bob)
118
119 fund(1_000_000)
120 testing.SetRealm(testing.NewUserRealm(bob))
121 Vouch(cross(cur), alice, "1 GNOT says this is a real person")
122 uassert.Equal(t, int64(1_000_000), BondedFor(alice))
123 uassert.Equal(t, int64(1_000_000), TotalBonded())
124 uassert.Equal(t, int64(0), Owed(bob), "nothing is owed while the vouch stands")
125
126 // Vouching again adds to the bond rather than replacing it.
127 fund(500_000)
128 testing.SetRealm(testing.NewUserRealm(bob))
129 Vouch(cross(cur), alice, "doubling down")
130 uassert.Equal(t, int64(1_500_000), BondedFor(alice))
131 uassert.Equal(t, int64(1_500_000), BondFrom(bob, alice))
132
133 // Revoking moves the bond to the ledger and nothing else.
134 fund(0)
135 testing.SetRealm(testing.NewUserRealm(bob))
136 refund := Revoke(cross(cur), alice)
137 uassert.Equal(t, int64(1_500_000), refund)
138 uassert.Equal(t, int64(0), BondedFor(alice))
139 uassert.Equal(t, int64(1_500_000), Owed(bob))
140 uassert.Equal(t, int64(1_500_000), TotalOwed())
141 uassert.Equal(t, before, balanceOf(bob), "revoking sends nothing")
142
143 // Withdrawing is what moves the coins, once.
144 fund(0)
145 testing.SetRealm(testing.NewUserRealm(bob))
146 got := Withdraw(cross(cur))
147 uassert.Equal(t, int64(1_500_000), got)
148 uassert.Equal(t, before+1_500_000, balanceOf(bob), "the bond came back")
149 uassert.Equal(t, int64(0), Owed(bob))
150 uassert.Equal(t, int64(0), TotalOwed())
151
152 fund(0)
153 testing.SetRealm(testing.NewUserRealm(bob))
154 uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
155 Withdraw(cross(cur))
156 })
157 uassert.Equal(t, before+1_500_000, balanceOf(bob), "and only once")
158}
159
160// A bond belongs to whoever posted it, not to whoever it was posted on.
161func TestOnlyTheVoucherGetsTheBondBack(cur realm, t *testing.T) {
162 reset()
163
164 fund(700_000)
165 testing.SetRealm(testing.NewUserRealm(bob))
166 Vouch(cross(cur), alice, "my money, my claim")
167
168 fund(0)
169 testing.SetRealm(testing.NewUserRealm(alice))
170 uassert.AbortsContains(t, cur, "no such vouch", func() {
171 Revoke(cross(cur), bob)
172 })
173
174 fund(0)
175 testing.SetRealm(testing.NewUserRealm(alice))
176 uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
177 Withdraw(cross(cur))
178 })
179 uassert.Equal(t, int64(700_000), BondedFor(alice), "the bond is untouched")
180}
181
182func TestIsTrustedIsTheGate(cur realm, t *testing.T) {
183 reset()
184
185 uassert.False(t, IsTrusted(alice, 1), "nobody starts trusted")
186 uassert.False(t, IsTrusted(alice, 0), "a zero min does not open the gate for nobody")
187
188 fund(0)
189 testing.SetRealm(testing.NewUserRealm(bob))
190 Vouch(cross(cur), alice, "one")
191 uassert.True(t, IsTrusted(alice, 1))
192 uassert.False(t, IsTrusted(alice, 2))
193
194 fund(0)
195 testing.SetRealm(testing.NewUserRealm(carol))
196 Vouch(cross(cur), alice, "two")
197 uassert.True(t, IsTrusted(alice, 2))
198 uassert.False(t, IsTrusted(alice, 3))
199
200 // The cheapest sybil shape, and the reason a real gate asks for two.
201 fund(0)
202 testing.SetRealm(testing.NewUserRealm(alice))
203 Vouch(cross(cur), bob, "back at you")
204 uassert.True(t, Mutual(alice, bob))
205 uassert.True(t, IsTrusted(bob, 1), "a mutual pair passes a gate set at one")
206 uassert.False(t, IsTrusted(bob, 2))
207}
208
209func TestTheTwoDirections(cur realm, t *testing.T) {
210 reset()
211
212 fund(0)
213 testing.SetRealm(testing.NewUserRealm(alice))
214 Vouch(cross(cur), bob, "a to b")
215 fund(0)
216 testing.SetRealm(testing.NewUserRealm(alice))
217 Vouch(cross(cur), carol, "a to c")
218 fund(0)
219 testing.SetRealm(testing.NewUserRealm(bob))
220 Vouch(cross(cur), carol, "b to c")
221
222 uassert.Equal(t, 2, len(VouchesOf(alice)))
223 uassert.Equal(t, 0, len(VouchedBy(alice)))
224 uassert.Equal(t, 2, len(VouchedBy(carol)))
225 uassert.Equal(t, 0, len(VouchesOf(carol)))
226 uassert.Equal(t, "b to c", ReasonFrom(bob, carol))
227 uassert.Equal(t, "", ReasonFrom(carol, bob))
228 uassert.Equal(t, int64(0), BondFrom(alice, bob), "an unbonded vouch is worth nothing")
229}
230
231// Badge is a borrowed read: a host realm calls it with no realm token and
232// gets a line pointing back at this realm's page.
233func TestBadge(cur realm, t *testing.T) {
234 reset()
235 uassert.True(t, strings.Contains(Badge(alice), "not vouched for"))
236
237 fund(2_000_000)
238 testing.SetRealm(testing.NewUserRealm(bob))
239 Vouch(cross(cur), alice, "one")
240
241 got := Badge(alice)
242 uassert.True(t, strings.Contains(got, "1 voucher]"), got)
243 uassert.True(t, strings.Contains(got, "2 GNOT bonded"), got)
244 uassert.True(t, strings.Contains(got, "/r/moul/x/social/vouch/v0:addr/"+alice.String()), got)
245}
246
247// TestRender uses uassert rather than an Example because every view contains
248// consecutive blank lines, which gno collapses inside an // Output: block.
249func TestRender(cur realm, t *testing.T) {
250 reset()
251
252 fund(3_000_000)
253 testing.SetRealm(testing.NewUserRealm(bob))
254 Vouch(cross(cur), alice, "a reason with [a link](x) and a | pipe")
255 fund(0)
256 testing.SetRealm(testing.NewUserRealm(carol))
257 Vouch(cross(cur), alice, "second voucher")
258
259 index := Render("")
260 uassert.True(t, strings.Contains(index, "| 2 | 1 | 3 GNOT | 0 GNOT |"),
261 "vouches, people, bonded, owed: "+index)
262 uassert.True(t, strings.Contains(index, `import "`+realmPath+`"`), "the index is the gate doc")
263 uassert.True(t, strings.Contains(index, "func=Vouch"), "the index offers the transaction")
264 uassert.True(t, strings.Contains(index, "\U0001F947"), "the board has a podium: "+index)
265
266 page := Render("addr/" + alice.String())
267 uassert.True(t, strings.Contains(page, alice.String()), "the page names the address")
268 uassert.True(t, strings.Contains(page, "a reason with \\[a link\\]\\(x\\) and a \\| pipe"),
269 "the reason is escaped where it is shown: "+page)
270 uassert.True(t, strings.Contains(page, "func=Revoke"))
271
272 // The other direction of the same edge, from the voucher's page.
273 vouchers := Render("addr/" + bob.String())
274 uassert.True(t, strings.Contains(vouchers, "This address vouches for nobody.") == false,
275 "bob vouches for alice: "+vouchers)
276 uassert.True(t, strings.Contains(vouchers, "Nobody vouches for this address."))
277
278 uassert.Equal(t, "# Vouch\nNot an address: nope", Render("addr/nope"))
279 uassert.True(t, strings.HasPrefix(Render("elsewhere"), "# Not found"))
280
281 // An empty graph says so rather than rendering an empty table.
282 reset()
283 uassert.True(t, strings.Contains(Render(""), "Nobody has been vouched for yet."))
284}
285
286func TestRenderOfAnUnknownAddress(cur realm, t *testing.T) {
287 reset()
288 page := Render("addr/" + carol.String())
289 uassert.True(t, strings.Contains(page, "Nobody vouches for this address."), page)
290 uassert.True(t, strings.Contains(page, "This address vouches for nobody."), page)
291 uassert.True(t, strings.Contains(page, "| 0 | 0 GNOT | no | 0 GNOT |"), page)
292}