1package royalty23import (4 "testing"56 "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)1213// 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 return19}2021func 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, royLedger25}2627func TestNewRoyalty(cur realm, t *testing.T) {28 tests := []struct {29 name string30 nilLedger bool31 maxBps int6432 wantPanic string // "" = success33 }{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 }4142 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 return49 }5051 _, 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 return57 }5859 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}6970func TestRoyaltyInfo(cur realm, t *testing.T) {71 artist := testutils.TestAddress("artist")72 special := testutils.TestAddress("special")7374 tests := []struct {75 name string76 setDefault bool77 defaultBps int6478 setToken bool79 tokenBps int6480 salePrice int6481 wantReceiver address82 wantAmount int6483 }{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 = 2113 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 }133134 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"))138139 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 }145146 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}153154func TestDefaultRoyalty(cur realm, t *testing.T) {155 artist := testutils.TestAddress("artist")156157 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 })164165 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 })173174 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)181182 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}188189func TestTokenRoyalty(cur realm, t *testing.T) {190 artist := testutils.TestAddress("artist")191 special := testutils.TestAddress("special")192193 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 })199200 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 })209210 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))215216 royLedger.DeleteTokenRoyalty("1")217 _, ok := roy.TokenRoyalty("1")218 uassert.False(t, ok)219220 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}226227func TestSetDefaultRoyaltyValidation(cur realm, t *testing.T) {228 artist := testutils.TestAddress("artist")229230 tests := []struct {231 name string232 receiver address233 bps int64234 wantErr error235 }{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 }242243 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 return252 }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}260261func TestSetTokenRoyaltyValidation(cur realm, t *testing.T) {262 artist := testutils.TestAddress("artist")263264 tests := []struct {265 name string266 mint bool267 tid grc721.TokenID268 receiver address269 bps int64270 wantErr error271 }{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 }279280 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 }286287 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 return293 }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}301302func 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)307308 urequire.NoError(t, tokenLedger.Mint(alice, "1"))309 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))310 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))311312 _, ok := roy.TokenRoyalty("1")313 uassert.True(t, ok)314315 urequire.NoError(t, tokenLedger.Burn("1"))316 _, ok = roy.TokenRoyalty("1")317 uassert.False(t, ok)318319 info, ok := roy.DefaultRoyalty()320 uassert.True(t, ok)321 uassert.Equal(t, artist, info.Receiver)322323 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}331332func 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)338339 urequire.NoError(t, tokenLedger.Mint(alice, "1"))340 urequire.NoError(t, royLedger.SetDefaultRoyalty(artist, 500))341 urequire.NoError(t, royLedger.SetTokenRoyalty("1", special, 250))342343 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)350351 royLedger.OnBurn("1")352 _, ok = roy.TokenRoyalty("1")353 uassert.False(t, ok)354 _, ok = roy.DefaultRoyalty()355 uassert.True(t, ok)356}357Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.