Skip to content

Commit

Permalink
take class whitelisting into account
Browse files Browse the repository at this point in the history
  • Loading branch information
Wojtek committed Oct 18, 2023
1 parent 48bd22c commit b045966
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 8 deletions.
88 changes: 88 additions & 0 deletions integration-tests/modules/assetnft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,94 @@ func TestAssetNFTIssueClassInvalidFeatures(t *testing.T) {
requireT.ErrorContains(err, "non-existing class feature provided")
}

// TestAssetNFTMintAndWhitelisting tests non-fungible token minting when whitelisting is required.
func TestAssetNFTMintAndWhitelisting(t *testing.T) {
t.Parallel()

ctx, chain := integrationtests.NewCoreumTestingContext(t)

requireT := require.New(t)
issuer := chain.GenAccount()
recipient := chain.GenAccount()

nftClient := nft.NewQueryClient(chain.ClientContext)
chain.FundAccountWithOptions(ctx, t, issuer, integration.BalancesOptions{
Messages: []sdk.Msg{
&assetnfttypes.MsgIssueClass{},
&assetnfttypes.MsgMint{},
&assetnfttypes.MsgAddToClassWhitelist{},
&assetnfttypes.MsgMint{},
},
Amount: chain.QueryAssetNFTParams(ctx, t).MintFee.Amount,
})

// issue new NFT class
issueMsg := &assetnfttypes.MsgIssueClass{
Issuer: issuer.String(),
Symbol: "NFTClassSymbol",
Features: []assetnfttypes.ClassFeature{
assetnfttypes.ClassFeature_whitelisting,
},
}
_, err := client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(issueMsg)),
issueMsg,
)
requireT.NoError(err)

classID := assetnfttypes.BuildClassID(issueMsg.Symbol, issuer)

// mint new token in that class - should fail
mintMsg := &assetnfttypes.MsgMint{
Sender: issuer.String(),
Recipient: recipient.String(),
ID: "id-1",
ClassID: classID,
URI: "https://my-class-meta.invalid/1",
URIHash: "content-hash",
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(mintMsg)),
mintMsg,
)
requireT.ErrorIs(err, cosmoserrors.ErrUnauthorized)

// whitelist class
msgAddToWhitelist := &assetnfttypes.MsgAddToClassWhitelist{
Sender: issuer.String(),
ClassID: classID,
Account: recipient.String(),
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgAddToWhitelist)),
msgAddToWhitelist,
)
requireT.NoError(err)

// mint again - should succeed
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(mintMsg)),
mintMsg,
)
requireT.NoError(err)

// check the owner
ownerRes, err := nftClient.Owner(ctx, &nft.QueryOwnerRequest{
ClassId: classID,
Id: mintMsg.ID,
})
requireT.NoError(err)
requireT.Equal(recipient.String(), ownerRes.Owner)
}

// TestAssetNFTMint tests non-fungible token minting.
func TestAssetNFTMint(t *testing.T) {
t.Parallel()
Expand Down
32 changes: 27 additions & 5 deletions x/asset/nft/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,13 @@ func (k Keeper) Mint(ctx sdk.Context, settings types.MintSettings) error {
}

if definition.IsFeatureEnabled(types.ClassFeature_whitelisting) && !definition.IsIssuer(settings.Recipient) {
return sdkerrors.Wrapf(cosmoserrors.ErrUnauthorized, "due to enabled whitelisting only the issuer can receive minted NFT, %s is not the issuer", settings.Recipient.String())
isWhitelisted, err := k.isClassWhitelisted(ctx, settings.ClassID, settings.Recipient)
if err != nil {
return err
}
if !isWhitelisted {
return sdkerrors.Wrapf(cosmoserrors.ErrUnauthorized, "due to enabled whitelisting only the issuer can receive minted NFT, %s is not the issuer", settings.Recipient.String())
}
}

if !k.nftKeeper.HasClass(ctx, settings.ClassID) {
Expand Down Expand Up @@ -573,17 +579,33 @@ func (k Keeper) IsWhitelisted(ctx sdk.Context, classID, nftID string, account sd
return false, sdkerrors.Wrapf(types.ErrFeatureDisabled, `feature "whitelisting" is disabled`)
}

if !k.nftKeeper.HasNFT(ctx, classID, nftID) {
return false, sdkerrors.Wrapf(types.ErrNFTNotFound, "nft with classID:%s and ID:%s not found", classID, nftID)
isWhitelisted, err := k.isTokenWhitelisted(ctx, classID, nftID, account)
if err != nil {
return false, err
}
if isWhitelisted {
return true, nil
}

return k.isClassWhitelisted(ctx, classID, account)
}

func (k Keeper) isClassWhitelisted(ctx sdk.Context, classID string, account sdk.AccAddress) (bool, error) {
if !k.nftKeeper.HasClass(ctx, classID) {
return false, sdkerrors.Wrapf(types.ErrNFTNotFound, "nft class with classID:%s not found", classID)
}

classKey, err := types.CreateClassWhitelistingKey(classID, account)
if err != nil {
return false, err
}

if bytes.Equal(ctx.KVStore(k.storeKey).Get(classKey), types.StoreTrue) {
return true, nil
return bytes.Equal(ctx.KVStore(k.storeKey).Get(classKey), types.StoreTrue), nil
}

func (k Keeper) isTokenWhitelisted(ctx sdk.Context, classID, nftID string, account sdk.AccAddress) (bool, error) {
if !k.nftKeeper.HasNFT(ctx, classID, nftID) {
return false, sdkerrors.Wrapf(types.ErrNFTNotFound, "nft with classID:%s and ID:%s not found", classID, nftID)
}

key, err := types.CreateWhitelistingKey(classID, nftID, account)
Expand Down
14 changes: 11 additions & 3 deletions x/asset/nft/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,17 @@ func TestKeeper_MintWithRecipientAndWhitelisting(t *testing.T) {
URIHash: "content-hash",
}

// mint NFT - should fail because recipient is not whitelisted, and cannot be because nft does not exist
err = nftKeeper.Mint(ctx, settings)
requireT.ErrorIs(err, cosmoserrors.ErrUnauthorized)
// mint NFT - should fail because recipient is not whitelisted
requireT.ErrorIs(nftKeeper.Mint(ctx, settings), cosmoserrors.ErrUnauthorized)

// whitelist for class
requireT.NoError(nftKeeper.AddToClassWhitelist(ctx, classID, addr, randomAddr))

// now minting should work
requireT.NoError(nftKeeper.Mint(ctx, settings))

// verify ownership
requireT.Equal(randomAddr, testApp.NFTKeeper.GetOwner(ctx, classID, settings.ID))
}

func TestKeeper_Burn(t *testing.T) {
Expand Down

0 comments on commit b045966

Please sign in to comment.