token_test.gno
10.41 Kb · 356 lines
1package royalty
2
3import (
4 "testing"
5
6 "gno.land/p/nt/grc721/v0"
7 "gno.land/p/nt/seqid/v0"
8 "gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10 "gno.land/p/nt/urequire/v0"
11)
12
13// newToken builds via same-realm cross (NewToken requires rlm.IsCurrent()).
14func newToken(name, symbol string, id seqid.ID, rlm realm) (tok *grc721.Token, led *grc721.PrivateLedger) {
15 func(cur realm) {
16 tok, led = grc721.NewToken(name, symbol, id, cur)
17 }(cross(rlm))
18 return
19}
20
21func newRoy(maxBps int64, rlm realm) (*grc721.PrivateLedger, *Royalty, *Ledger) {
22 _, tokenLedger := newToken("Foo", "FOO", 0, rlm)
23 roy, royLedger := NewRoyalty(tokenLedger, maxBps)
24 return tokenLedger, roy, royLedger
25}
26
27func TestNewRoyalty(cur realm, t *testing.T) {
28 tests := []struct {
29 name string
30 nilLedger bool
31 maxBps int64
32 wantPanic string // "" = success
33 }{
34 {"nil token ledger panics", true, 500, "royalty: nil core ledger"},
35 {"maxBps above FeeDenominator panics", false, 10001, ErrMaxBpsRange.Error()},
36 {"negative maxBps panics", false, -1, ErrMaxBpsRange.Error()},
37 {"maxBps at FeeDenominator boundary constructs", false, 10000, ""},
38 {"maxBps zero boundary constructs", false, 0, ""},
39 {"typical maxBps constructs", false, 1000, ""},
40 }
41
42 for _, tt := range tests {
43 t.Run(tt.name, func(t *testing.T) {
44 if tt.nilLedger {
45 uassert.PanicsWithMessage(t, cur, tt.wantPanic, func() {
46 NewRoyalty(nil, tt.maxBps)
47 })
48 return
49 }
50
51 _, tokenLedger := newToken("Foo", "FOO", 0, cur)
52 if tt.wantPanic != "" {
53 uassert.PanicsWithMessage(t, cur, tt.wantPanic, func() {
54 NewRoyalty(tokenLedger, tt.maxBps)
55 })
56 return
57 }
58
59 roy, royLedger := NewRoyalty(tokenLedger, tt.maxBps)
60 uassert.True(t, roy != nil)
61 uassert.True(t, royLedger != nil)
62 uassert.Equal(t, Kind, roy.ExtensionKind())
63 uassert.Equal(t, "royalty", roy.ExtensionKind())
64 uassert.Equal(t, tt.maxBps, roy.MaxBps())
65 uassert.Equal(t, tokenLedger.ReadToken().ID(), roy.TokenID())
66 })
67 }
68}
69
70func TestRoyaltyInfo(cur realm, t *testing.T) {
71 artist := testutils.TestAddress("artist")
72 special := testutils.TestAddress("special")
73
74 tests := []struct {
75 name string
76 setDefault bool
77 defaultBps int64
78 setToken bool
79 tokenBps int64
80 salePrice int64
81 wantReceiver address
82 wantAmount int64
83 }{
84 {
85 name: "no royalty configured returns zero receiver and amount",
86 salePrice: 10000,
87 wantReceiver: zeroAddress,
88 wantAmount: 0,
89 },
90 {
91 name: "default 5% yields 500 on salePrice 10000",
92 setDefault: true,
93 defaultBps: 500,
94 salePrice: 10000,
95 wantReceiver: artist,
96 wantAmount: 500,
97 },
98 {
99 name: "per-token override takes precedence over default",
100 setDefault: true,
101 defaultBps: 500,
102 setToken: true,
103 tokenBps: 250,
104 salePrice: 10000,
105 wantReceiver: special,
106 wantAmount: 250,
107 },
108 {
109 name: "fractional bps 250 truncates on small sale price",
110 setToken: true,
111 tokenBps: 250,
112 salePrice: 100, // 100*250/10000 = 2
113 wantReceiver: special,
114 wantAmount: 2,
115 },
116 {
117 name: "zero sale price yields zero amount",
118 setDefault: true,
119 defaultBps: 500,
120 salePrice: 0,
121 wantReceiver: artist,
122 wantAmount: 0,
123 },
124 {
125 name: "default receiver applies when token has no override",
126 setDefault: true,
127 defaultBps: 1000,
128 salePrice: 5000,
129 wantReceiver: artist,
130 wantAmount: 500,
131 },
132 }
133
134 for _, tt := range tests {
135 t.Run(tt.name, func(t *testing.T) {
136 tokenLedger, roy, royLedger := newRoy(1000, cur)
137 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), "1"))
138
139 if tt.setDefault {
140 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, tt.defaultBps))
141 }
142 if tt.setToken {
143 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, tt.tokenBps))
144 }
145
146 recv, amt, err := roy.RoyaltyInfo("1", tt.salePrice)
147 urequire.NoError(t, err)
148 uassert.Equal(t, tt.wantReceiver, recv)
149 uassert.Equal(t, tt.wantAmount, amt)
150 })
151 }
152}
153
154func TestDefaultRoyalty(cur realm, t *testing.T) {
155 artist := testutils.TestAddress("artist")
156
157 t.Run("unset default reports not present", func(t *testing.T) {
158 _, roy, _ := newRoy(1000, cur)
159 info, ok := roy.DefaultRoyalty()
160 uassert.False(t, ok)
161 uassert.Equal(t, zeroAddress, info.Receiver)
162 uassert.Equal(t, int64(0), info.Bps)
163 })
164
165 t.Run("set default is reported and readable", func(t *testing.T) {
166 _, roy, royLedger := newRoy(1000, cur)
167 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))
168 info, ok := roy.DefaultRoyalty()
169 uassert.True(t, ok)
170 uassert.Equal(t, artist, info.Receiver)
171 uassert.Equal(t, int64(500), info.Bps)
172 })
173
174 t.Run("delete default clears it", func(t *testing.T) {
175 _, roy, royLedger := newRoy(1000, cur)
176 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))
177 royLedger.DeleteDefaultRoyalty()
178 info, ok := roy.DefaultRoyalty()
179 uassert.False(t, ok)
180 uassert.Equal(t, zeroAddress, info.Receiver)
181
182 recv, amt, err := roy.RoyaltyInfo("1", 10000)
183 urequire.NoError(t, err)
184 uassert.Equal(t, zeroAddress, recv)
185 uassert.Equal(t, int64(0), amt)
186 })
187}
188
189func TestTokenRoyalty(cur realm, t *testing.T) {
190 artist := testutils.TestAddress("artist")
191 special := testutils.TestAddress("special")
192
193 t.Run("unset per-token override reports not present", func(t *testing.T) {
194 _, roy, _ := newRoy(1000, cur)
195 info, ok := roy.TokenRoyalty("1")
196 uassert.False(t, ok)
197 uassert.Equal(t, zeroAddress, info.Receiver)
198 })
199
200 t.Run("set per-token override is reported and readable", func(t *testing.T) {
201 tokenLedger, roy, royLedger := newRoy(1000, cur)
202 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), "1"))
203 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))
204 info, ok := roy.TokenRoyalty("1")
205 uassert.True(t, ok)
206 uassert.Equal(t, special, info.Receiver)
207 uassert.Equal(t, int64(250), info.Bps)
208 })
209
210 t.Run("delete per-token override falls back to default", func(t *testing.T) {
211 tokenLedger, roy, royLedger := newRoy(1000, cur)
212 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), "1"))
213 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))
214 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))
215
216 royLedger.DeleteTokenRoyalty("1")
217 _, ok := roy.TokenRoyalty("1")
218 uassert.False(t, ok)
219
220 recv, amt, err := roy.RoyaltyInfo("1", 10000)
221 urequire.NoError(t, err)
222 uassert.Equal(t, artist, recv)
223 uassert.Equal(t, int64(500), amt)
224 })
225}
226
227func TestSetDefaultRoyaltyValidation(cur realm, t *testing.T) {
228 artist := testutils.TestAddress("artist")
229
230 tests := []struct {
231 name string
232 receiver address
233 bps int64
234 wantErr error
235 }{
236 {"bps above maxBps returns ErrInvalidBps", artist, 1001, ErrInvalidBps},
237 {"negative bps returns ErrInvalidBps", artist, -1, ErrInvalidBps},
238 {"invalid (zero) receiver returns ErrInvalidReceiver", zeroAddress, 100, ErrInvalidReceiver},
239 {"bps at maxBps boundary succeeds", artist, 1000, nil},
240 {"zero bps succeeds", artist, 0, nil},
241 }
242
243 for _, tt := range tests {
244 t.Run(tt.name, func(t *testing.T) {
245 _, roy, royLedger := newRoy(1000, cur)
246 err := royLedger.SetDefaultRoyalty(tt.receiver, tt.bps)
247 if tt.wantErr != nil {
248 uassert.ErrorIs(t, err, tt.wantErr)
249 _, ok := roy.DefaultRoyalty()
250 uassert.False(t, ok)
251 return
252 }
253 urequire.NoError(t, err)
254 info, ok := roy.DefaultRoyalty()
255 uassert.True(t, ok)
256 uassert.Equal(t, tt.bps, info.Bps)
257 })
258 }
259}
260
261func TestSetTokenRoyaltyValidation(cur realm, t *testing.T) {
262 artist := testutils.TestAddress("artist")
263
264 tests := []struct {
265 name string
266 mint bool
267 tid grc721.TokenID
268 receiver address
269 bps int64
270 wantErr error
271 }{
272 {"unminted token returns ErrTokenNotMinted", false, "404", artist, 100, ErrTokenNotMinted},
273 {"minted token with bps above maxBps returns ErrInvalidBps", true, "1", artist, 1001, ErrInvalidBps},
274 {"minted token with negative bps returns ErrInvalidBps", true, "1", artist, -1, ErrInvalidBps},
275 {"minted token with invalid receiver returns ErrInvalidReceiver", true, "1", zeroAddress, 100, ErrInvalidReceiver},
276 {"minted token at maxBps boundary succeeds", true, "1", artist, 1000, nil},
277 {"minted token with valid override succeeds", true, "1", artist, 250, nil},
278 }
279
280 for _, tt := range tests {
281 t.Run(tt.name, func(t *testing.T) {
282 tokenLedger, roy, royLedger := newRoy(1000, cur)
283 if tt.mint {
284 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), tt.tid))
285 }
286
287 err := royLedger.SetTokenRoyalty(tt.tid, tt.receiver, tt.bps)
288 if tt.wantErr != nil {
289 uassert.ErrorIs(t, err, tt.wantErr)
290 _, ok := roy.TokenRoyalty(tt.tid)
291 uassert.False(t, ok)
292 return
293 }
294 urequire.NoError(t, err)
295 info, ok := roy.TokenRoyalty(tt.tid)
296 uassert.True(t, ok)
297 uassert.Equal(t, tt.bps, info.Bps)
298 })
299 }
300}
301
302func TestOnBurnClearsTokenRoyaltyKeepsDefault(cur realm, t *testing.T) {
303 alice := testutils.TestAddress("alice")
304 artist := testutils.TestAddress("artist")
305 special := testutils.TestAddress("special")
306 tokenLedger, roy, royLedger := newRoy(1000, cur)
307
308 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
309 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))
310 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))
311
312 _, ok := roy.TokenRoyalty("1")
313 uassert.True(t, ok)
314
315 urequire.NoError(t, tokenLedger.Burn("1"))
316 _, ok = roy.TokenRoyalty("1")
317 uassert.False(t, ok)
318
319 info, ok := roy.DefaultRoyalty()
320 uassert.True(t, ok)
321 uassert.Equal(t, artist, info.Receiver)
322
323 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
324 _, ok = roy.TokenRoyalty("1")
325 uassert.False(t, ok)
326 recv, amt, err := roy.RoyaltyInfo("1", 10000)
327 urequire.NoError(t, err)
328 uassert.Equal(t, artist, recv)
329 uassert.Equal(t, int64(500), amt)
330}
331
332func TestHooks(cur realm, t *testing.T) {
333 alice := testutils.TestAddress("alice")
334 bob := testutils.TestAddress("bob")
335 artist := testutils.TestAddress("artist")
336 special := testutils.TestAddress("special")
337 tokenLedger, roy, royLedger := newRoy(1000, cur)
338
339 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
340 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))
341 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))
342
343 royLedger.OnMint(alice, "1")
344 royLedger.OnTransfer(alice, bob, "1")
345 info, ok := roy.TokenRoyalty("1")
346 uassert.True(t, ok)
347 uassert.Equal(t, special, info.Receiver)
348 _, ok = roy.DefaultRoyalty()
349 uassert.True(t, ok)
350
351 royLedger.OnBurn("1")
352 _, ok = roy.TokenRoyalty("1")
353 uassert.False(t, ok)
354 _, ok = roy.DefaultRoyalty()
355 uassert.True(t, ok)
356}