Skip to content

Commit

Permalink
Added SendAuthorization for sending specific NFTs. (#678)
Browse files Browse the repository at this point in the history
# Description

# Reviewers checklist:
- [ ] Try to write more meaningful comments with clear actions to be taken.
- [ ] Nit-picking should be unblocking. Focus on core issues.

# Authors checklist
- [x] Provide a concise and meaningful description
- [x] Review the code yourself first, before making the PR.
- [x] Annotate your PR in places that require explanation.
- [x] Think and try to split the PR to smaller PR if it is big.
  • Loading branch information
miladz68 authored Oct 25, 2023
1 parent f3af2f2 commit 915968e
Show file tree
Hide file tree
Showing 7 changed files with 949 additions and 0 deletions.
51 changes: 51 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@

- [Msg](#coreum.asset.ft.v1.Msg)

- [coreum/asset/nft/v1/authz.proto](#coreum/asset/nft/v1/authz.proto)
- [NFTIdentifier](#coreum.asset.nft.v1.NFTIdentifier)
- [SendAuthorization](#coreum.asset.nft.v1.SendAuthorization)

- [coreum/asset/nft/v1/event.proto](#coreum/asset/nft/v1/event.proto)
- [EventAddedToClassWhitelist](#coreum.asset.nft.v1.EventAddedToClassWhitelist)
- [EventAddedToWhitelist](#coreum.asset.nft.v1.EventAddedToWhitelist)
Expand Down Expand Up @@ -1224,6 +1228,53 @@ Msg defines the Msg service.



<a name="coreum/asset/nft/v1/authz.proto"></a>
<p align="right"><a href="#top">Top</a></p>

## coreum/asset/nft/v1/authz.proto



<a name="coreum.asset.nft.v1.NFTIdentifier"></a>

### NFTIdentifier



| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `class_id` | [string](#string) | | class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 |
| `id` | [string](#string) | | id defines the unique identification of nft |






<a name="coreum.asset.nft.v1.SendAuthorization"></a>

### SendAuthorization
SendAuthorization allows the grantee to send specific NFTs from the granter's account.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `nfts` | [NFTIdentifier](#coreum.asset.nft.v1.NFTIdentifier) | repeated | |





<!-- end messages -->

<!-- end enums -->

<!-- end HasExtensions -->

<!-- end services -->



<a name="coreum/asset/nft/v1/event.proto"></a>
<p align="right"><a href="#top">Top</a></p>

Expand Down
140 changes: 140 additions & 0 deletions integration-tests/modules/assetnft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1875,3 +1875,143 @@ func TestAssetNFTClassWhitelist(t *testing.T) {
)
requireT.NoError(err)
}

// TestAssetNFTSendAuthorization tests that assetnft SendAuthorization works as expected.
func TestAssetNFTSendAuthorization(t *testing.T) {
t.Parallel()

ctx, chain := integrationtests.NewCoreumTestingContext(t)

requireT := require.New(t)
granter := chain.GenAccount()
grantee := chain.GenAccount()
recipient := chain.GenAccount()
nftClient := nft.NewQueryClient(chain.ClientContext)
authzClient := authztypes.NewQueryClient(chain.ClientContext)

chain.FundAccountWithOptions(ctx, t, granter, integration.BalancesOptions{
Messages: []sdk.Msg{
&assetnfttypes.MsgIssueClass{},
&assetnfttypes.MsgMint{},
&authztypes.MsgGrant{},
},
Amount: chain.QueryAssetNFTParams(ctx, t).MintFee.Amount,
})

chain.FundAccountWithOptions(ctx, t, grantee, integration.BalancesOptions{
Amount: sdk.NewInt(1),
})

// issue new NFT class
issueMsg := &assetnfttypes.MsgIssueClass{
Issuer: granter.String(),
Symbol: "NFTClassSymbol",
Features: []assetnfttypes.ClassFeature{},
}

// mint new token in that class
classID := assetnfttypes.BuildClassID(issueMsg.Symbol, granter)
nftID := "id-1"
mintMsg1 := &assetnfttypes.MsgMint{
Sender: granter.String(),
ID: nftID,
ClassID: classID,
}

msgList := []sdk.Msg{issueMsg, mintMsg1}
_, err := client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(granter),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgList...)),
msgList...,
)
requireT.NoError(err)

// try to send before grant
sendMsg := &nft.MsgSend{
ClassId: classID,
Id: nftID,
Sender: granter.String(),
Receiver: recipient.String(),
}
execMsg := authztypes.NewMsgExec(grantee, []sdk.Msg{sendMsg})

chain.FundAccountWithOptions(ctx, t, grantee, integration.BalancesOptions{
Messages: []sdk.Msg{
&execMsg,
&execMsg,
},
})

_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(grantee),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(&execMsg)),
&execMsg,
)
requireT.Error(err)
requireT.ErrorIs(err, authztypes.ErrNoAuthorizationFound)

// grant authorization to send nft
grantMsg, err := authztypes.NewMsgGrant(
granter,
grantee,
assetnfttypes.NewSendAuthorization([]assetnfttypes.NFTIdentifier{
{ClassId: classID, Id: nftID},
{ClassId: classID, Id: "not-minted-yet"},
}),
lo.ToPtr(time.Now().Add(time.Minute)),
)
requireT.NoError(err)
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(granter),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(grantMsg)),
grantMsg,
)
requireT.NoError(err)

// assert granted
gransRes, err := authzClient.Grants(ctx, &authztypes.QueryGrantsRequest{
Granter: granter.String(),
Grantee: grantee.String(),
})
requireT.NoError(err)
requireT.Equal(1, len(gransRes.Grants))
updatedGrant := assetnfttypes.SendAuthorization{}
chain.ClientContext.Codec().MustUnmarshal(gransRes.Grants[0].Authorization.Value, &updatedGrant)
requireT.ElementsMatch([]assetnfttypes.NFTIdentifier{
{ClassId: classID, Id: nftID},
{ClassId: classID, Id: "not-minted-yet"},
}, updatedGrant.Nfts)

// try to send after grant
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(grantee),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(&execMsg)),
&execMsg,
)
requireT.NoError(err)

// assert transfer of ownership
ownerResp, err := nftClient.Owner(ctx, &nft.QueryOwnerRequest{
ClassId: classID,
Id: nftID,
})
requireT.NoError(err)
requireT.EqualValues(ownerResp.Owner, recipient.String())

// assert granted
gransRes, err = authzClient.Grants(ctx, &authztypes.QueryGrantsRequest{
Granter: granter.String(),
Grantee: grantee.String(),
})
requireT.NoError(err)
requireT.Equal(1, len(gransRes.Grants))
updatedGrant = assetnfttypes.SendAuthorization{}
chain.ClientContext.Codec().MustUnmarshal(gransRes.Grants[0].Authorization.Value, &updatedGrant)
requireT.ElementsMatch([]assetnfttypes.NFTIdentifier{
{ClassId: classID, Id: "not-minted-yet"},
}, updatedGrant.Nfts)
}
25 changes: 25 additions & 0 deletions proto/coreum/asset/nft/v1/authz.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";
package coreum.asset.nft.v1;

import "amino/amino.proto";
import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";

option go_package = "github.com/CoreumFoundation/coreum/v3/x/asset/nft/types";

// SendAuthorization allows the grantee to send specific NFTs from the granter's account.
message SendAuthorization {
option (cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization";
option (amino.name) = "cosmos-sdk/nft/SendAuthorization";
repeated NFTIdentifier nfts = 1 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}

message NFTIdentifier {
// class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721
string class_id = 1;
// id defines the unique identification of nft
string id = 2;
}
Loading

0 comments on commit 915968e

Please sign in to comment.