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

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}