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

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}