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

vouch_test.gno

8.01 Kb · 243 lines
  1package vouch
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8	"gno.land/p/nt/uassert/v0"
  9	"gno.land/p/nt/urequire/v0"
 10)
 11
 12var (
 13	alice = testutils.TestAddress("alice")
 14	bob   = testutils.TestAddress("bob")
 15	carol = testutils.TestAddress("carol")
 16	dave  = testutils.TestAddress("dave")
 17)
 18
 19const realmPath = "gno.land/r/moul/x/social/vouch/v0"
 20
 21func TestValidReason(t *testing.T) {
 22	tests := []struct {
 23		name   string
 24		reason string
 25		want   bool
 26	}{
 27		{"plain", "worked with them for a year", true},
 28		{"markdown is allowed and escaped later", "[x](y) | z", true},
 29		{"at the limit", strings.Repeat("x", MaxReasonLen), true},
 30		{"empty", "", false},
 31		{"blank", "   ", false},
 32		{"too long", strings.Repeat("x", MaxReasonLen+1), false},
 33		{"a reason is one line", "met them\nat a conference", false},
 34		{"no tabs either", "met them\there", false},
 35		{"control character", "a\x01b", false},
 36		{"delete character", "a\x7fb", false},
 37	}
 38	for _, tt := range tests {
 39		uassert.Equal(t, tt.want, ValidReason(tt.reason), tt.name)
 40	}
 41}
 42
 43func TestRecordRefusesWhatItCannotStore(t *testing.T) {
 44	g := NewGraph()
 45
 46	_, err := g.Record(alice, alice, "myself", 0, 100)
 47	uassert.ErrorIs(t, err, ErrSelfVouch)
 48
 49	_, err = g.Record(alice, bob, "", 0, 100)
 50	uassert.ErrorIs(t, err, ErrBadReason)
 51
 52	_, err = g.Record(alice, bob, "fine", -1, 100)
 53	uassert.ErrorIs(t, err, ErrBadBond)
 54
 55	uassert.Equal(t, 0, g.Count())
 56	uassert.Equal(t, 0, g.People())
 57	uassert.Equal(t, 0, g.ScoreOf(alice))
 58}
 59
 60// A score counts people. This is the property every caller of IsTrusted is
 61// relying on, so it is the one worth pinning hardest.
 62func TestScoreCountsPeopleAndNotVouches(t *testing.T) {
 63	g := NewGraph()
 64
 65	updated, err := g.Record(bob, alice, "known them for years", 0, 100)
 66	urequire.NoError(t, err)
 67	uassert.False(t, updated, "the first vouch is not an update")
 68	uassert.Equal(t, 1, g.ScoreOf(alice))
 69
 70	// Saying it again is an edit, not a second voice.
 71	updated, err = g.Record(bob, alice, "still true", 0, 101)
 72	urequire.NoError(t, err)
 73	uassert.True(t, updated, "the second vouch replaced the first")
 74	uassert.Equal(t, 1, g.ScoreOf(alice), "one person is one point, however often they say it")
 75	uassert.Equal(t, "still true", g.ReasonFrom(bob, alice), "the reason was updated")
 76	uassert.Equal(t, 1, g.Count())
 77
 78	// A different person does move it.
 79	_, err = g.Record(carol, alice, "we shipped together", 0, 102)
 80	urequire.NoError(t, err)
 81	uassert.Equal(t, 2, g.ScoreOf(alice))
 82	uassert.Equal(t, 2, g.Count())
 83	uassert.Equal(t, 1, g.People(), "one address has been vouched for")
 84
 85	// And revoking lowers it.
 86	_, err = g.Revoke(carol, alice)
 87	urequire.NoError(t, err)
 88	uassert.Equal(t, 1, g.ScoreOf(alice))
 89	uassert.Equal(t, 1, g.Count())
 90}
 91
 92func TestIsTrusted(t *testing.T) {
 93	g := NewGraph()
 94	g.Record(bob, alice, "one", 0, 100)
 95	g.Record(carol, alice, "two", 0, 100)
 96
 97	tests := []struct {
 98		name string
 99		addr address
100		min  int
101		want bool
102	}{
103		{"below the score", alice, 1, true},
104		{"at the score", alice, 2, true},
105		{"above the score", alice, 3, false},
106		{"zero is read as one", alice, 0, true},
107		{"zero does not open the gate for nobody", dave, 0, false},
108		{"a negative min is read as one too", dave, -5, false},
109		{"nobody vouches for dave", dave, 1, false},
110	}
111	for _, tt := range tests {
112		uassert.Equal(t, tt.want, g.IsTrusted(tt.addr, tt.min), tt.name)
113	}
114}
115
116func TestTheBondFollowsTheVouchInBothDirections(t *testing.T) {
117	g := NewGraph()
118
119	_, err := g.Record(bob, alice, "staking 1 GNOT on this", 1_000_000, 100)
120	urequire.NoError(t, err)
121	uassert.Equal(t, int64(1_000_000), g.BondedFor(alice))
122	uassert.Equal(t, int64(1_000_000), g.TotalBonded())
123	uassert.Equal(t, int64(0), g.Owed(bob), "nothing is owed while the vouch stands")
124
125	// A repeated vouch adds to the bond rather than replacing it.
126	_, err = g.Record(bob, alice, "doubling down", 500_000, 101)
127	urequire.NoError(t, err)
128	uassert.Equal(t, int64(1_500_000), g.BondedFor(alice))
129	uassert.Equal(t, int64(1_500_000), g.BondFrom(bob, alice))
130
131	// A second voucher's bond piles on top.
132	g.Record(carol, alice, "and me", 250_000, 102)
133	uassert.Equal(t, int64(1_750_000), g.BondedFor(alice))
134
135	// Revoking returns exactly what that voucher put in, and nothing else.
136	refund, err := g.Revoke(bob, alice)
137	urequire.NoError(t, err)
138	uassert.Equal(t, int64(1_500_000), refund)
139	uassert.Equal(t, int64(250_000), g.BondedFor(alice), "carol's bond stays")
140	uassert.Equal(t, int64(250_000), g.TotalBonded())
141	uassert.Equal(t, int64(1_500_000), g.Owed(bob))
142	uassert.Equal(t, int64(1_500_000), g.TotalOwed())
143
144	// Withdrawing zeroes the credit before the realm sends anything.
145	got, err := g.Withdraw(bob)
146	urequire.NoError(t, err)
147	uassert.Equal(t, int64(1_500_000), got)
148	uassert.Equal(t, int64(0), g.Owed(bob))
149	uassert.Equal(t, int64(0), g.TotalOwed())
150
151	// A reentrant withdrawal finds nothing.
152	_, err = g.Withdraw(bob)
153	uassert.ErrorIs(t, err, ErrNothingOwed)
154}
155
156func TestRevokeRefusesWhatWasNeverThere(t *testing.T) {
157	g := NewGraph()
158	_, err := g.Revoke(alice, bob)
159	uassert.ErrorIs(t, err, ErrNoVouch)
160
161	g.Record(alice, bob, "fine", 0, 100)
162	_, err = g.Revoke(bob, alice)
163	uassert.ErrorIs(t, err, ErrNoVouch, "a vouch is directed, so the other way round is not one")
164}
165
166func TestTheTwoDirections(t *testing.T) {
167	g := NewGraph()
168	g.Record(alice, bob, "a to b", 0, 100)
169	g.Record(alice, carol, "a to c", 0, 100)
170	g.Record(bob, carol, "b to c", 0, 100)
171
172	uassert.Equal(t, 0, len(g.VouchedBy(alice)), "nobody vouches for alice")
173	uassert.Equal(t, 2, len(g.VouchesOf(alice)))
174	uassert.Equal(t, 2, len(g.VouchedBy(carol)))
175	uassert.Equal(t, 0, len(g.VouchesOf(carol)))
176
177	// Sorted by address, so two identical calls render identically.
178	in := g.VouchedBy(carol)
179	uassert.True(t, in[0].String() < in[1].String(), "inbound is sorted")
180
181	uassert.Equal(t, "a to c", g.ReasonFrom(alice, carol))
182	uassert.Equal(t, "", g.ReasonFrom(carol, alice), "no vouch, no reason")
183}
184
185func TestMutual(t *testing.T) {
186	g := NewGraph()
187	g.Record(alice, bob, "a to b", 0, 100)
188
189	uassert.False(t, g.Mutual(alice, bob), "one direction is not mutual")
190	uassert.False(t, g.Mutual(alice, alice), "an address is never mutual with itself")
191
192	g.Record(bob, alice, "b to a", 0, 101)
193	uassert.True(t, g.Mutual(alice, bob))
194	uassert.True(t, g.Mutual(bob, alice), "mutual is symmetric")
195	uassert.False(t, g.Mutual(alice, carol))
196
197	// The cheapest sybil shape: two addresses, two transactions, both pass a
198	// gate set at one. That is the whole reason Mutual is exported.
199	uassert.True(t, g.IsTrusted(alice, 1))
200	uassert.True(t, g.IsTrusted(bob, 1))
201	uassert.False(t, g.IsTrusted(alice, 2))
202}
203
204func TestLeaderboardIsATotalOrder(t *testing.T) {
205	g := NewGraph()
206	g.Record(alice, carol, "one", 0, 100)
207	g.Record(bob, carol, "two", 0, 100)
208	g.Record(alice, bob, "three", 100, 100)
209
210	rows := g.Leaderboard(10)
211	urequire.Equal(t, 2, len(rows))
212	uassert.Equal(t, carol.String(), rows[0].Addr.String(), "highest score first")
213	uassert.Equal(t, 2, rows[0].Score)
214	uassert.Equal(t, int64(100), rows[1].Bonded)
215
216	uassert.Equal(t, 1, len(g.Leaderboard(1)), "limit truncates")
217	uassert.Equal(t, 0, len(g.Leaderboard(0)))
218	uassert.Equal(t, 0, len(NewGraph().Leaderboard(10)), "an empty graph ranks nobody")
219
220	// Revoking the last vouch for an address drops it off the board.
221	g.Revoke(alice, bob)
222	uassert.Equal(t, 1, len(g.Leaderboard(10)))
223	uassert.Equal(t, 0, g.ScoreOf(bob))
224}
225
226func TestBadge(t *testing.T) {
227	g := NewGraph()
228	uassert.True(t, strings.Contains(Badge(realmPath, alice, g.ScoreOf(alice), 0), "not vouched for"))
229
230	g.Record(bob, alice, "one", 2_000_000, 100)
231	got := Badge(realmPath, alice, g.ScoreOf(alice), g.BondedFor(alice))
232	uassert.True(t, strings.Contains(got, "1 voucher]"), "singular: "+got)
233	uassert.True(t, strings.Contains(got, "2 GNOT bonded"), got)
234	uassert.True(t, strings.Contains(got, AddrURL(realmPath, alice)), got)
235
236	g.Record(carol, alice, "two", 0, 101)
237	uassert.True(t, strings.Contains(Badge(realmPath, alice, g.ScoreOf(alice), 0), "2 vouchers]"))
238}
239
240func TestURLsStripTheChainDomain(t *testing.T) {
241	uassert.Equal(t, "/r/moul/x/social/vouch/v0", RealmURL(realmPath))
242	uassert.Equal(t, "/r/a/b:addr/"+alice.String(), AddrURL("gno.land/r/a/b", alice))
243}