grc20reg_test.gno
7.83 Kb · 226 lines
1package grc20reg
2
3import (
4 "chain"
5 "strings"
6 "testing"
7
8 "gno.land/p/demo/tokens/grc20"
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11 "gno.land/p/nt/urequire/v0"
12)
13
14func TestRegistry(cur realm, t *testing.T) {
15 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/foo"))
16 token, ledger := grc20.NewToken("TestToken", "TST", 4, 0, cur)
17 ledger.Mint(cur.Address(), 1234567)
18 // register
19 key := Register(cross(cur), token, "mySlug")
20 regToken := Get(key)
21 urequire.True(t, regToken != nil, "expected to find a token") // fixme: use urequire.NotNil
22 urequire.Equal(t, regToken.GetSymbol(), "TST")
23
24 expected := `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo).TST - [info](/r/demo/grc20reg:gno.land/r/demo/foo.TST)
25`
26 got := Render("")
27 urequire.True(t, strings.Contains(got, expected))
28 // 404
29 invalidToken := Get("0xdeadbeef")
30 urequire.True(t, invalidToken == nil)
31
32 got = Render("")
33 urequire.True(t, strings.Contains(got, expected))
34
35 expected = `# TestToken
36- symbol: **TST**
37- realm: [gno.land/r/demo/foo](/r/demo/foo).TST
38- decimals: 4
39- total supply: 1234567
40`
41 got = Render(key)
42 urequire.Equal(t, expected, got)
43
44 // The registry keys by rlmPath.symbol, so a second token with the same
45 // symbol in the same realm is rejected even though its Token.ID() differs
46 // (distinct trailing sequence id). See TestRegisterRejectsOverwrite.
47 second, _ := grc20.NewToken("Second", "TST", 4, 1, cur)
48 urequire.NotEqual(t, token.ID(), second.ID()) // ids are decoupled from symbol
49 urequire.AbortsContains(t, cur, "token already registered", func() {
50 Register(cross(cur), second, "")
51 })
52}
53
54func TestRegistryWrappers(cur realm, t *testing.T) {
55 const (
56 tokenPath = "gno.land/r/demo/token"
57 realmCaller = "gno.land/r/demo/vault"
58 )
59 alice := testutils.TestAddress("alice")
60 bob := testutils.TestAddress("bob")
61 carol := testutils.TestAddress("carol")
62 realmAddr := chain.PackageAddress(realmCaller)
63
64 testing.SetRealm(testing.NewCodeRealm(tokenPath))
65 token, ledger := grc20.NewToken("TestToken", "TST", 4, 0, cur)
66 urequire.NoError(t, ledger.Mint(alice, 1_000))
67 urequire.NoError(t, ledger.Mint(realmAddr, 200))
68 tokenKey := Register(cross(cur), token, "")
69 uassert.Equal(t, "TestToken", MustGet(tokenKey).GetName())
70 uassert.Equal(t, "TST", MustGet(tokenKey).GetSymbol())
71 uassert.Equal(t, 4, MustGet(tokenKey).GetDecimals())
72 uassert.Equal(t, int64(1_200), MustGet(tokenKey).TotalSupply())
73 uassert.Equal(t, int64(1_000), MustGet(tokenKey).BalanceOf(alice))
74 uassert.Equal(t, int64(0), MustGet(tokenKey).Allowance(alice, carol))
75
76 testing.SetRealm(testing.NewUserRealm(alice))
77 Transfer(cross(cur), tokenKey, bob, 100)
78 Approve(cross(cur), tokenKey, carol, 300)
79
80 testing.SetRealm(testing.NewUserRealm(carol))
81 TransferFrom(cross(cur), tokenKey, alice, bob, 200)
82 uassert.Equal(t, int64(700), MustGet(tokenKey).BalanceOf(alice))
83 uassert.Equal(t, int64(300), MustGet(tokenKey).BalanceOf(bob))
84 uassert.Equal(t, int64(100), MustGet(tokenKey).Allowance(alice, carol))
85
86 testing.SetOriginCaller(alice)
87 testing.SetRealm(testing.NewCodeRealm(realmCaller))
88 Transfer(cross(cur), tokenKey, bob, 50)
89 uassert.Equal(t, int64(150), MustGet(tokenKey).BalanceOf(realmAddr))
90 uassert.Equal(t, int64(700), MustGet(tokenKey).BalanceOf(alice))
91 uassert.Equal(t, int64(350), MustGet(tokenKey).BalanceOf(bob))
92
93 testing.SetRealm(testing.NewUserRealm(alice))
94 uassert.AbortsWithMessage(t, cur, "insufficient balance", func() {
95 Transfer(cross(cur), tokenKey, bob, 701)
96 })
97 uassert.Equal(t, int64(700), MustGet(tokenKey).BalanceOf(alice))
98 uassert.Equal(t, int64(350), MustGet(tokenKey).BalanceOf(bob))
99}
100
101func viaWrapper(cur realm, tokenKey string, to address, amount int64) {
102 Transfer(cross(cur), tokenKey, to, amount)
103}
104
105func viaCallerTeller(cur realm, token *grc20.Token, to address, amount int64) error {
106 return token.CallerTeller().Transfer(0, cur, to, amount)
107}
108
109func TestRegistryWrapperCallerSemantics(cur realm, t *testing.T) {
110 const tokenPath = "gno.land/r/demo/token_caller_semantics"
111 alice := testutils.TestAddress("alice")
112 bob := testutils.TestAddress("bob")
113 intermediary := cur.Address()
114
115 testing.SetRealm(testing.NewCodeRealm(tokenPath))
116 token, ledger := grc20.NewToken("CallerSemantics", "CALL", 4, 0, cur)
117 urequire.NoError(t, ledger.Mint(alice, 1_000))
118 urequire.NoError(t, ledger.Mint(intermediary, 1_000))
119 tokenKey := Register(cross(cur), token, "")
120
121 testing.SetRealm(testing.NewUserRealm(alice))
122
123 // ViaWrapper adds another registry frame, so CallerTeller sees the
124 // intermediary realm rather than alice.
125 viaWrapper(cross(cur), tokenKey, bob, 100)
126 uassert.Equal(t, int64(1_000), token.BalanceOf(alice))
127 uassert.Equal(t, int64(900), token.BalanceOf(intermediary))
128
129 // ViaCallerTeller has no extra frame, so CallerTeller sees alice.
130 urequire.NoError(t, viaCallerTeller(cross(cur), token, bob, 100))
131 uassert.Equal(t, int64(900), token.BalanceOf(alice))
132 uassert.Equal(t, int64(900), token.BalanceOf(intermediary))
133 uassert.Equal(t, int64(200), token.BalanceOf(bob))
134}
135
136func TestRegisterRejectsOverwrite(cur realm, t *testing.T) {
137 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/grc20reg_overwrite"))
138 token, ledger := grc20.NewToken("Bar", "BAR", 4, 0, cur)
139 ledger.Mint(cur.Address(), 11)
140 key := Register(cross(cur), token, "")
141
142 urequire.Equal(t, "BAR", Get(key).GetSymbol())
143 urequire.Equal(t, int64(11), Get(key).BalanceOf(cur.Address()))
144
145 replacement, _ := grc20.NewToken("Replacement", "BAR", 6, 0, cur)
146 urequire.AbortsContains(t, cur, "token already registered", func() {
147 Register(cross(cur), replacement, "")
148 })
149}
150
151func TestRegisterRejectsAliasedTokenPaths(cur realm, t *testing.T) {
152 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/grc20reg_alias"))
153 token, _ := grc20.NewToken("Aliased Token", "ALIAS", 4, 0, cur)
154 Register(cross(cur), token, "first")
155
156 urequire.AbortsContains(t, cur, "token already registered", func() {
157 Register(cross(cur), token, "second")
158 })
159}
160
161func TestRegisterRejectsTokenFromDifferentRealm(cur realm, t *testing.T) {
162 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/grc20reg_id_source"))
163 token, _ := grc20.NewToken("Mismatch Token", "MISMATCH", 4, 0, cur)
164
165 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/grc20reg_id_target"))
166 urequire.AbortsContains(t, cur, "token must be registered from its own realm", func() {
167 Register(cross(cur), token, "")
168 })
169}
170
171func TestValidateSlug(cur realm, t *testing.T) {
172 // Valid slugs — should not panic
173 valid := []string{"mytoken", "my-token", "my_token", "Token123", "a", "A-B_c", strings.Repeat("a", maxSlugLen)}
174 for _, slug := range valid {
175 validateSlug(slug) // no panic = pass
176 }
177}
178
179func TestValidateSlugPanicsOnTooLong(cur realm, t *testing.T) {
180 defer func() { recover() }()
181 validateSlug(strings.Repeat("a", maxSlugLen+1))
182 t.Errorf("should have panicked")
183}
184
185func TestValidateSlugPanicsOnSpace(cur realm, t *testing.T) {
186 defer func() { recover() }()
187 validateSlug("has space")
188 t.Errorf("should have panicked")
189}
190
191func TestValidateSlugPanicsOnDot(cur realm, t *testing.T) {
192 defer func() { recover() }()
193 validateSlug("has.dot")
194 t.Errorf("should have panicked")
195}
196
197func TestValidateSlugPanicsOnSlash(cur realm, t *testing.T) {
198 defer func() { recover() }()
199 validateSlug("has/slash")
200 t.Errorf("should have panicked")
201}
202
203func TestValidateSlugPanicsOnBrackets(cur realm, t *testing.T) {
204 defer func() { recover() }()
205 validateSlug("[brackets]")
206 t.Errorf("should have panicked")
207}
208
209func TestValidateSlugPanicsOnParens(cur realm, t *testing.T) {
210 defer func() { recover() }()
211 validateSlug("(parens)")
212 t.Errorf("should have panicked")
213}
214
215func TestValidateSlugPanicsOnInjection(cur realm, t *testing.T) {
216 defer func() { recover() }()
217 validateSlug(`) [Claim](https://evil.com`)
218 t.Errorf("should have panicked")
219}
220
221func TestRegisterRejectsNilToken(cur realm, t *testing.T) {
222 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/grc20reg_nil"))
223 urequire.AbortsContains(t, cur, "nil token", func() {
224 Register(cross(cur), nil, "")
225 })
226}