tellers_test.gno
7.29 Kb · 290 lines
1package grc721
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7 "gno.land/p/nt/urequire/v0"
8)
9
10type evilTeller struct{ Teller }
11
12func TestIsCanonicalTeller(cur realm, t *testing.T) {
13 tok, led := newTestToken("Foo", "FOO", 0, cur)
14
15 var realmTeller Teller
16 func(cur realm) {
17 realmTeller = tok.RealmTeller(0, cur)
18 }(cross(cur))
19
20 tests := []struct {
21 name string
22 te Teller
23 want bool
24 }{
25 {"CallerTeller is canonical", led.CallerTeller(), true},
26 {"ReadonlyTeller is canonical", tok.ReadonlyTeller(), true},
27 {"RealmTeller is canonical", realmTeller, true},
28 {"ImpersonateTeller is canonical", led.ImpersonateTeller(alice), true},
29 {"embedding wrapper is rejected", &evilTeller{led.CallerTeller()}, false},
30 }
31
32 for _, tc := range tests {
33 tc := tc
34 t.Run(tc.name, func(t *testing.T) {
35 uassert.Equal(t, tc.want, IsCanonicalTeller(tc.te))
36 })
37 }
38}
39
40func TestTellerNilGuards(cur realm, t *testing.T) {
41 var nilTok *Token
42 var nilLed *PrivateLedger
43
44 tests := []struct {
45 name string
46 msg string
47 fn func()
48 }{
49 {"CallerTeller on nil ledger panics", "Ledger cannot be nil", func() {
50 nilLed.CallerTeller()
51 }},
52 {"ReadonlyTeller on nil token panics", "Token cannot be nil", func() {
53 nilTok.ReadonlyTeller()
54 }},
55 {"RealmTeller on nil token panics", "Token cannot be nil", func() {
56 nilTok.RealmTeller(0, cur)
57 }},
58 {"RealmSubTeller on nil token panics", "Token cannot be nil", func() {
59 nilTok.RealmSubTeller(0, cur, "slug")
60 }},
61 {"ImpersonateTeller on nil ledger panics", "Ledger cannot be nil", func() {
62 nilLed.ImpersonateTeller(alice)
63 }},
64 }
65
66 for _, tc := range tests {
67 tc := tc
68 t.Run(tc.name, func(t *testing.T) {
69 uassert.PanicsWithMessage(t, cur, tc.msg, tc.fn)
70 })
71 }
72}
73
74func TestReadonlyTeller(cur realm, t *testing.T) {
75 tok, led := newTestToken("Foo", "FOO", 0, cur)
76 urequire.NoError(t, led.Mint(alice, "1"))
77 ro := tok.ReadonlyTeller()
78
79 tests := []struct {
80 name string
81 op string
82 }{
83 {"Approve rejected with ErrReadonly", "approve"},
84 {"TransferFrom rejected with ErrReadonly", "transfer"},
85 {"SetApprovalForAll rejected with ErrReadonly", "setall"},
86 }
87
88 for _, tc := range tests {
89 tc := tc
90 t.Run(tc.name, func(t *testing.T) {
91 var err error
92 testing.SetRealm(testing.NewUserRealm(alice))
93 func(cur realm) {
94 switch tc.op {
95 case "approve":
96 err = ro.Approve(0, cur, bob, "1")
97 case "transfer":
98 err = ro.TransferFrom(0, cur, alice, bob, "1")
99 case "setall":
100 err = ro.SetApprovalForAll(0, cur, bob, true)
101 }
102 }(cross(cur))
103 uassert.ErrorIs(t, err, ErrReadonly)
104 })
105 }
106}
107
108func TestCallerTellerFlow(cur realm, t *testing.T) {
109 tok, led := newTestToken("Foo", "FOO", 0, cur)
110 urequire.NoError(t, led.Mint(alice, "1"))
111 urequire.NoError(t, led.Mint(alice, "2"))
112
113 teller := led.CallerTeller()
114
115 testing.SetRealm(testing.NewUserRealm(alice))
116 func(cur realm) {
117 urequire.NoError(t, teller.Approve(0, cur, bob, "1"))
118 }(cross(cur))
119
120 testing.SetRealm(testing.NewUserRealm(alice))
121 func(cur realm) {
122 urequire.NoError(t, teller.SetApprovalForAll(0, cur, bob, true))
123 }(cross(cur))
124 uassert.True(t, tok.IsApprovedForAll(alice, bob))
125
126 testing.SetRealm(testing.NewUserRealm(bob))
127 func(cur realm) {
128 urequire.NoError(t, teller.TransferFrom(0, cur, alice, carl, "1"))
129 }(cross(cur))
130
131 owner, err := tok.OwnerOf("1")
132 urequire.NoError(t, err)
133 uassert.Equal(t, carl, owner)
134
135 testing.SetRealm(testing.NewUserRealm(bob))
136 func(cur realm) {
137 uassert.ErrorIs(t, teller.TransferFrom(0, cur, carl, bob, "1"), ErrCallerIsNotOwnerOrApproved)
138 }(cross(cur))
139}
140
141func TestCallerTellerSpoofedRealm(cur realm, t *testing.T) {
142 _, led := newTestToken("Foo", "FOO", 0, cur)
143 urequire.NoError(t, led.Mint(alice, "1"))
144 teller := led.CallerTeller()
145
146 // Stale outer cur while a fresh frame is current → ErrSpoofedRealm.
147 tests := []struct {
148 name string
149 op string
150 }{
151 {"Approve rejects spoofed realm", "approve"},
152 {"TransferFrom rejects spoofed realm", "transfer"},
153 {"SetApprovalForAll rejects spoofed realm", "setall"},
154 }
155
156 for _, tc := range tests {
157 tc := tc
158 t.Run(tc.name, func(t *testing.T) {
159 var err error
160 stale := cur
161 func(cur realm) {
162 switch tc.op {
163 case "approve":
164 err = teller.Approve(0, stale, bob, "1")
165 case "transfer":
166 err = teller.TransferFrom(0, stale, alice, bob, "1")
167 case "setall":
168 err = teller.SetApprovalForAll(0, stale, bob, true)
169 }
170 }(cross(cur))
171 uassert.ErrorIs(t, err, ErrSpoofedRealm)
172 })
173 }
174}
175
176func TestRealmTellerFlow(cur realm, t *testing.T) {
177 tok, led := newTestToken("Foo", "FOO", 0, cur)
178
179 var teller Teller
180 var realmAddr address
181 func(cur realm) {
182 teller = tok.RealmTeller(0, cur)
183 realmAddr = cur.Address()
184 }(cross(cur))
185
186 urequire.NoError(t, led.Mint(realmAddr, "1"))
187
188 testing.SetRealm(testing.NewUserRealm(alice))
189 func(cur realm) {
190 urequire.NoError(t, teller.TransferFrom(0, cur, realmAddr, bob, "1"))
191 }(cross(cur))
192
193 owner, err := tok.OwnerOf("1")
194 urequire.NoError(t, err)
195 uassert.Equal(t, bob, owner)
196}
197
198func TestRealmSubTellerFlow(cur realm, t *testing.T) {
199 tok, led := newTestToken("Foo", "FOO", 0, cur)
200
201 tests := []struct {
202 name string
203 slug string
204 tid TokenID
205 wantsRealmAddr bool
206 }{
207 {"empty slug derives the realm address itself", "", "10", true},
208 {"non-empty slug derives a sub-account", "vault", "11", false},
209 }
210
211 for _, tc := range tests {
212 tc := tc
213 t.Run(tc.name, func(t *testing.T) {
214 var teller Teller
215 var realmAddr, account address
216 func(cur realm) {
217 teller = tok.RealmSubTeller(0, cur, tc.slug)
218 realmAddr = cur.Address()
219 account = accountSlugAddr(cur.Address(), tc.slug)
220 }(cross(cur))
221
222 if tc.wantsRealmAddr {
223 uassert.Equal(t, realmAddr, account)
224 } else {
225 uassert.True(t, realmAddr != account, "sub-account must differ from realm address")
226 }
227
228 urequire.NoError(t, led.Mint(account, tc.tid))
229 testing.SetRealm(testing.NewUserRealm(alice))
230 func(cur realm) {
231 urequire.NoError(t, teller.TransferFrom(0, cur, account, bob, tc.tid))
232 }(cross(cur))
233
234 owner, err := tok.OwnerOf(tc.tid)
235 urequire.NoError(t, err)
236 uassert.Equal(t, bob, owner)
237 })
238 }
239}
240
241func TestRealmTellerSpoofedRealm(cur realm, t *testing.T) {
242 tok, _ := newTestToken("Foo", "FOO", 0, cur)
243
244 tests := []struct {
245 name string
246 fn func()
247 }{
248 {"RealmTeller rejects spoofed realm", func() {
249 stale := cur
250 func(cur realm) {
251 tok.RealmTeller(0, stale)
252 }(cross(cur))
253 }},
254 {"RealmSubTeller rejects spoofed realm", func() {
255 stale := cur
256 func(cur realm) {
257 tok.RealmSubTeller(0, stale, "vault")
258 }(cross(cur))
259 }},
260 }
261
262 for _, tc := range tests {
263 tc := tc
264 t.Run(tc.name, func(t *testing.T) {
265 urequire.AbortsContains(t, cur, "rlm does not match the current crossing frame", tc.fn)
266 })
267 }
268}
269
270func TestImpersonateTeller(cur realm, t *testing.T) {
271 tok, led := newTestToken("Foo", "FOO", 0, cur)
272 urequire.NoError(t, led.Mint(alice, "1"))
273
274 teller := led.ImpersonateTeller(alice)
275 testing.SetRealm(testing.NewUserRealm(bob))
276 func(cur realm) {
277 urequire.NoError(t, teller.TransferFrom(0, cur, alice, bob, "1"))
278 }(cross(cur))
279
280 owner, err := tok.OwnerOf("1")
281 urequire.NoError(t, err)
282 uassert.Equal(t, bob, owner)
283}
284
285func TestImpersonateTellerRejectsInvalidAddress(cur realm, t *testing.T) {
286 _, led := newTestToken("Foo", "FOO", 0, cur)
287 uassert.PanicsWithMessage(t, cur, "invalid address", func() {
288 led.ImpersonateTeller(zeroAddress)
289 })
290}