Skip to content

Commit

Permalink
Send minted FT and NFT tokens to recipient (#667)
Browse files Browse the repository at this point in the history
# Description

Minted FT and NFT tokens might be stored on the provided account, different than issuer.

# 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
wojtek-coreum authored Oct 11, 2023
1 parent f52a2ee commit f54d178
Show file tree
Hide file tree
Showing 15 changed files with 440 additions and 195 deletions.
2 changes: 2 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ MsgIssue defines message to issue new fungible token.
| ----- | ---- | ----- | ----------- |
| `sender` | [string](#string) | | |
| `coin` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | |
| `recipient` | [string](#string) | | |



Expand Down Expand Up @@ -1876,6 +1877,7 @@ MsgMint defines message for the Mint method.
| `uri` | [string](#string) | | |
| `uri_hash` | [string](#string) | | |
| `data` | [google.protobuf.Any](#google.protobuf.Any) | | |
| `recipient` | [string](#string) | | |



Expand Down
25 changes: 25 additions & 0 deletions integration-tests/modules/assetft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ func TestAssetFTMint(t *testing.T) {
assertT := assert.New(t)
issuer := chain.GenAccount()
randomAddress := chain.GenAccount()
recipient := chain.GenAccount()
bankClient := banktypes.NewQueryClient(chain.ClientContext)

chain.FundAccountWithOptions(ctx, t, issuer, integration.BalancesOptions{
Expand All @@ -434,6 +435,7 @@ func TestAssetFTMint(t *testing.T) {
&assetfttypes.MsgIssue{},
&assetfttypes.MsgMint{},
&assetfttypes.MsgMint{},
&assetfttypes.MsgMint{},
},
Amount: chain.QueryAssetFTParams(ctx, t).IssueFee.Amount.MulRaw(2),
})
Expand Down Expand Up @@ -547,6 +549,29 @@ func TestAssetFTMint(t *testing.T) {
newSupply, err := bankClient.SupplyOf(ctx, &banktypes.QuerySupplyOfRequest{Denom: mintableDenom})
requireT.NoError(err)
assertT.EqualValues(mintCoin, newSupply.GetAmount().Sub(oldSupply.GetAmount()))

// mint tokens to recipient
mintCoin = sdk.NewCoin(mintableDenom, sdkmath.NewInt(10))
mintMsg = &assetfttypes.MsgMint{
Sender: issuer.String(),
Recipient: recipient.String(),
Coin: mintCoin,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(mintMsg)),
mintMsg,
)
requireT.NoError(err)

balance, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{Address: recipient.String(), Denom: mintableDenom})
requireT.NoError(err)
assertT.EqualValues(mintCoin.String(), balance.GetBalance().String())

newSupply2, err := bankClient.SupplyOf(ctx, &banktypes.QuerySupplyOfRequest{Denom: mintableDenom})
requireT.NoError(err)
assertT.EqualValues(mintCoin, newSupply2.GetAmount().Sub(newSupply.GetAmount()))
}

// TestAssetFTBurn tests burn functionality of fungible tokens.
Expand Down
24 changes: 24 additions & 0 deletions integration-tests/modules/assetnft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ func TestAssetNFTMint(t *testing.T) {
&assetnfttypes.MsgIssueClass{},
&assetnfttypes.MsgMint{},
&nft.MsgSend{},
&assetnfttypes.MsgMint{},
},
Amount: chain.QueryAssetNFTParams(ctx, t).MintFee.Amount,
})
Expand Down Expand Up @@ -433,6 +434,29 @@ func TestAssetNFTMint(t *testing.T) {
requireT.NoError(err)
requireT.Equal(recipient.String(), ownerRes.Owner)

// mint to recipient

mintMsg = &assetnfttypes.MsgMint{
Sender: issuer.String(),
Recipient: recipient.String(),
ID: "id-2",
ClassID: classID,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(mintMsg)),
mintMsg,
)
requireT.NoError(err)

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

// check that balance is 0 meaning mint fee was taken

bankClient := banktypes.NewQueryClient(chain.ClientContext)
Expand Down
1 change: 1 addition & 0 deletions proto/coreum/asset/ft/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ message MsgIssue {
message MsgMint {
string sender = 1;
cosmos.base.v1beta1.Coin coin = 2 [(gogoproto.nullable) = false];
string recipient = 3;
}

message MsgBurn {
Expand Down
1 change: 1 addition & 0 deletions proto/coreum/asset/nft/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ message MsgMint {
string uri = 4 [(gogoproto.customname) = "URI"];
string uri_hash = 5 [(gogoproto.customname) = "URIHash"];
google.protobuf.Any data = 6;
string recipient = 7;
}

// MsgBurn defines message for the Burn method.
Expand Down
4 changes: 2 additions & 2 deletions x/asset/ft/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func (k Keeper) SetDenomMetadata(ctx sdk.Context, denom, symbol, description str
}

// Mint mints new fungible token.
func (k Keeper) Mint(ctx sdk.Context, sender sdk.AccAddress, coin sdk.Coin) error {
func (k Keeper) Mint(ctx sdk.Context, sender, recipient sdk.AccAddress, coin sdk.Coin) error {
def, err := k.GetDefinition(ctx, coin.Denom)
if err != nil {
return sdkerrors.Wrapf(err, "not able to get token info for denom:%s", coin.Denom)
Expand All @@ -307,7 +307,7 @@ func (k Keeper) Mint(ctx sdk.Context, sender sdk.AccAddress, coin sdk.Coin) erro
return err
}

return k.mintIfReceivable(ctx, def, coin.Amount, sender)
return k.mintIfReceivable(ctx, def, coin.Amount, recipient)
}

// Burn burns fungible token.
Expand Down
17 changes: 14 additions & 3 deletions x/asset/ft/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func TestKeeper_Mint(t *testing.T) {
requireT.Equal(types.BuildDenom(settings.Symbol, settings.Issuer), unmintableDenom)

// try to mint unmintable token
err = ftKeeper.Mint(ctx, addr, sdk.NewCoin(unmintableDenom, sdkmath.NewInt(100)))
err = ftKeeper.Mint(ctx, addr, addr, sdk.NewCoin(unmintableDenom, sdkmath.NewInt(100)))
requireT.ErrorIs(err, types.ErrFeatureDisabled)

// Issue a mintable fungible token
Expand All @@ -398,11 +398,11 @@ func TestKeeper_Mint(t *testing.T) {

// try to mint as non-issuer
randomAddr := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())
err = ftKeeper.Mint(ctx, randomAddr, sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)))
err = ftKeeper.Mint(ctx, randomAddr, randomAddr, sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)))
requireT.ErrorIs(err, cosmoserrors.ErrUnauthorized)

// mint tokens and check balance and total supply
err = ftKeeper.Mint(ctx, addr, sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)))
err = ftKeeper.Mint(ctx, addr, addr, sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)))
requireT.NoError(err)

balance := bankKeeper.GetBalance(ctx, addr, mintableDenom)
Expand All @@ -411,6 +411,17 @@ func TestKeeper_Mint(t *testing.T) {
totalSupply, err := bankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
requireT.NoError(err)
requireT.EqualValues(sdkmath.NewInt(877), totalSupply.Supply.AmountOf(mintableDenom))

// mint to another account
err = ftKeeper.Mint(ctx, addr, randomAddr, sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)))
requireT.NoError(err)

balance = bankKeeper.GetBalance(ctx, randomAddr, mintableDenom)
requireT.EqualValues(sdk.NewCoin(mintableDenom, sdkmath.NewInt(100)), balance)

totalSupply, err = bankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{})
requireT.NoError(err)
requireT.EqualValues(sdkmath.NewInt(977), totalSupply.Supply.AmountOf(mintableDenom))
}

func TestKeeper_Burn(t *testing.T) {
Expand Down
12 changes: 10 additions & 2 deletions x/asset/ft/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var _ types.MsgServer = MsgServer{}
// MsgKeeper defines subscope of keeper methods required by msg service.
type MsgKeeper interface {
Issue(ctx sdk.Context, settings types.IssueSettings) (string, error)
Mint(ctx sdk.Context, sender sdk.AccAddress, coin sdk.Coin) error
Mint(ctx sdk.Context, sender, recipient sdk.AccAddress, coin sdk.Coin) error
Burn(ctx sdk.Context, sender sdk.AccAddress, coin sdk.Coin) error
Freeze(ctx sdk.Context, sender, addr sdk.AccAddress, coin sdk.Coin) error
Unfreeze(ctx sdk.Context, sender, addr sdk.AccAddress, coin sdk.Coin) error
Expand Down Expand Up @@ -70,7 +70,15 @@ func (ms MsgServer) Mint(goCtx context.Context, req *types.MsgMint) (*types.Empt
return nil, sdkerrors.Wrap(cosmoserrors.ErrInvalidAddress, "invalid sender address")
}

err = ms.keeper.Mint(ctx, sender, req.Coin)
recipient := sender
if req.Recipient != "" {
recipient, err = sdk.AccAddressFromBech32(req.Recipient)
if err != nil {
return nil, sdkerrors.Wrap(cosmoserrors.ErrInvalidAddress, "invalid recipient address")
}
}

err = ms.keeper.Mint(ctx, sender, recipient, req.Coin)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit f54d178

Please sign in to comment.