Skip to content

Commit

Permalink
Use frozen coins for bank keeper SpendableBalance.
Browse files Browse the repository at this point in the history
  • Loading branch information
dzmitryhil committed Oct 24, 2023
1 parent 84503b3 commit acc7151
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 5 deletions.
5 changes: 2 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ import (
"github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/nft"
nftkeeper "github.com/cosmos/cosmos-sdk/x/nft/keeper"
"github.com/cosmos/cosmos-sdk/x/params"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
Expand Down Expand Up @@ -104,9 +106,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cast"

"github.com/cosmos/cosmos-sdk/x/nft"
nftkeeper "github.com/cosmos/cosmos-sdk/x/nft/keeper"

"github.com/CoreumFoundation/coreum/v3/app/openapi"
appupgrade "github.com/CoreumFoundation/coreum/v3/app/upgrade"
appupgradev1 "github.com/CoreumFoundation/coreum/v3/app/upgrade/v1"
Expand Down
206 changes: 206 additions & 0 deletions integration-tests/modules/assetft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ func TestBalanceQuery(t *testing.T) {
&assetfttypes.MsgIssue{},
&assetfttypes.MsgSetWhitelistedLimit{},
&assetfttypes.MsgFreeze{},
&assetfttypes.MsgGloballyFreeze{},
&banktypes.MsgSend{},
},
Amount: issueFee,
Expand Down Expand Up @@ -391,6 +392,211 @@ func TestBalanceQuery(t *testing.T) {
assertT.Equal(frozenCoin.Amount.String(), resp.Frozen.String())
assertT.Equal(sendCoin.Amount.String(), resp.Balance.String())
assertT.Equal("0", resp.Locked.String())

// freeze globally now

msgGloballyFreeze := &assetfttypes.MsgGloballyFreeze{
Sender: issuer.String(),
Denom: denom,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgGloballyFreeze)),
msgGloballyFreeze,
)
require.NoError(t, err)

resp, err = ftClient.Balance(ctx, &assetfttypes.QueryBalanceRequest{
Account: recipient.String(),
Denom: denom,
})
require.NoError(t, err)

bankClient := banktypes.NewQueryClient(chain.ClientContext)
recipientBalanceRes, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{
Address: recipient.String(),
Denom: denom,
})
require.NoError(t, err)

assertT.Equal(whitelistedCoin.Amount.String(), resp.Whitelisted.String())
assertT.Equal(recipientBalanceRes.Balance.Amount.String(), resp.Frozen.String())
assertT.Equal(sendCoin.Amount.String(), resp.Balance.String())
assertT.Equal("0", resp.Locked.String())
}

func TestSpendableBalanceQuery(t *testing.T) {
t.Parallel()

ctx, chain := integrationtests.NewCoreumTestingContext(t)

requireT := require.New(t)
bankClient := banktypes.NewQueryClient(chain.ClientContext)

issueFee := chain.QueryAssetFTParams(ctx, t).IssueFee.Amount

issuer := chain.GenAccount()
recipient := chain.GenAccount()

chain.FundAccountWithOptions(ctx, t, issuer, integration.BalancesOptions{
Messages: []sdk.Msg{
&assetfttypes.MsgIssue{},
&assetfttypes.MsgIssue{},
&banktypes.MsgSend{},
&banktypes.MsgSend{},
&assetfttypes.MsgFreeze{},
&assetfttypes.MsgFreeze{},
&assetfttypes.MsgGloballyFreeze{},
},
Amount: issueFee.MulRaw(2),
})

// issue the new fungible token form issuer
msgIssue := &assetfttypes.MsgIssue{
Issuer: issuer.String(),
Symbol: "WBTC",
Subunit: "wsatoshi",
Precision: 8,
InitialAmount: sdkmath.NewInt(200),
BurnRate: sdk.NewDec(0),
SendCommissionRate: sdk.NewDec(0),
Features: []assetfttypes.Feature{assetfttypes.Feature_freezing},
}
_, err := client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgIssue)),
msgIssue,
)
require.NoError(t, err)

denom1 := assetfttypes.BuildDenom(msgIssue.Subunit, issuer)
frozenCoin1 := sdk.NewInt64Coin(denom1, 20)
sendCoin1 := sdk.NewInt64Coin(denom1, 100)

msgSend := &banktypes.MsgSend{
FromAddress: issuer.String(),
ToAddress: recipient.String(),
Amount: sdk.NewCoins(sendCoin1),
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgSend)),
msgSend,
)
require.NoError(t, err)

recipientSpendableBalanceBeforeFreesRes, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient.String(),
Denom: denom1,
})
require.NoError(t, err)
requireT.Equal(sendCoin1.Amount.String(), recipientSpendableBalanceBeforeFreesRes.Balance.Amount.String())

msgFreeze := &assetfttypes.MsgFreeze{
Sender: issuer.String(),
Account: recipient.String(),
Coin: frozenCoin1,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgFreeze)),
msgFreeze,
)
require.NoError(t, err)

recipientSpendableBalanceAfterFreezeRes, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient.String(),
Denom: denom1,
})
require.NoError(t, err)
requireT.Equal(sendCoin1.Amount.Sub(frozenCoin1.Amount).String(), recipientSpendableBalanceAfterFreezeRes.Balance.Amount.String())

// freeze globally now
msgGloballyFreeze := &assetfttypes.MsgGloballyFreeze{
Sender: issuer.String(),
Denom: denom1,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgGloballyFreeze)),
msgGloballyFreeze,
)
require.NoError(t, err)

recipientSpendableBalanceAfterGlobalFreezeRes, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient.String(),
Denom: denom1,
})
require.NoError(t, err)
requireT.Equal(sdkmath.ZeroInt().String(), recipientSpendableBalanceAfterGlobalFreezeRes.Balance.Amount.String())

// issue one more token
msgIssue = &assetfttypes.MsgIssue{
Issuer: issuer.String(),
Symbol: "WBTC2",
Subunit: "wsatoshi2",
Precision: 8,
InitialAmount: sdkmath.NewInt(200),
BurnRate: sdk.NewDec(0),
SendCommissionRate: sdk.NewDec(0),
Features: []assetfttypes.Feature{assetfttypes.Feature_freezing},
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgIssue)),
msgIssue,
)
require.NoError(t, err)

denom2 := assetfttypes.BuildDenom(msgIssue.Subunit, issuer)
frozenCoin2 := sdk.NewInt64Coin(denom2, 20)
sendCoin2 := sdk.NewInt64Coin(denom2, 100)

msgSend = &banktypes.MsgSend{
FromAddress: issuer.String(),
ToAddress: recipient.String(),
Amount: sdk.NewCoins(sendCoin2),
}

_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgSend)),
msgSend,
)
require.NoError(t, err)

recipientSpendableBalancesBeforeFreezeRes, err := bankClient.SpendableBalances(ctx, &banktypes.QuerySpendableBalancesRequest{
Address: recipient.String(),
})
require.NoError(t, err)
require.Len(t, recipientSpendableBalancesBeforeFreezeRes.Balances, 2)
requireT.Equal(sendCoin2.Amount.String(), recipientSpendableBalancesBeforeFreezeRes.Balances.AmountOf(denom2).String())

msgFreeze = &assetfttypes.MsgFreeze{
Sender: issuer.String(),
Account: recipient.String(),
Coin: frozenCoin2,
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgFreeze)),
msgFreeze,
)
require.NoError(t, err)

recipientSpendableBalancesBeforeFreezeRes, err = bankClient.SpendableBalances(ctx, &banktypes.QuerySpendableBalancesRequest{
Address: recipient.String(),
})
require.NoError(t, err)
requireT.Equal(sendCoin2.Amount.Sub(frozenCoin2.Amount).String(), recipientSpendableBalancesBeforeFreezeRes.Balances.AmountOf(denom2).String())
}

// TestEmptyBalanceQuery tests balance query.
Expand Down
3 changes: 3 additions & 0 deletions x/asset/ft/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ func (k Keeper) GetFrozenBalances(ctx sdk.Context, addr sdk.AccAddress, paginati

// GetFrozenBalance returns the frozen balance of a denom and account.
func (k Keeper) GetFrozenBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
if k.isGloballyFrozen(ctx, denom) {
return k.bankKeeper.GetBalance(ctx, addr, denom)
}
return k.frozenAccountBalanceStore(ctx, addr).Balance(denom)
}

Expand Down
2 changes: 1 addition & 1 deletion x/asset/nft/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type NFTKeeper interface {
Burn(ctx sdk.Context, classID, nftID string) error
Update(ctx sdk.Context, n nft.NFT) error
GetOwner(ctx sdk.Context, classID, nftID string) sdk.AccAddress
Transfer(ctx sdk.Context, classID string, nftID string, receiver sdk.AccAddress) error
Transfer(ctx sdk.Context, classID, nftID string, receiver sdk.AccAddress) error
}

// BankKeeper defines the expected bank interface.
Expand Down
56 changes: 56 additions & 0 deletions x/wbank/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package keeper

import (
"context"

sdkerrors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -98,3 +101,56 @@ func (k BaseKeeperWrapper) InputOutputCoins(ctx sdk.Context, inputs []banktypes.

return k.BaseKeeper.InputOutputCoins(ctx, inputs, outputs)
}

// ********** Query server **********

// SpendableBalances implements a gRPC query handler for retrieving an account's spendable balances including asset ft
// frozen coins.
func (k BaseKeeperWrapper) SpendableBalances(ctx context.Context, req *banktypes.QuerySpendableBalancesRequest) (*banktypes.QuerySpendableBalancesResponse, error) {
res, err := k.BaseKeeper.SpendableBalances(ctx, req)
if err != nil {
return nil, err
}
addr, err := sdk.AccAddressFromBech32(req.Address)
if err != nil {
return nil, sdkerrors.Wrapf(cosmoserrors.ErrInvalidAddress, "invalid address %s", req.Address)
}
for i := range res.Balances {
spendableCoin := k.getSpendableCoin(sdk.UnwrapSDKContext(ctx), addr, res.Balances[i])
res.Balances[i] = spendableCoin
}

return res, nil
}

// SpendableBalanceByDenom implements a gRPC query handler for retrieving an account's spendable balance for a specific
// denom, including asset ft frozen coins.
func (k BaseKeeperWrapper) SpendableBalanceByDenom(ctx context.Context, req *banktypes.QuerySpendableBalanceByDenomRequest) (*banktypes.QuerySpendableBalanceByDenomResponse, error) {
res, err := k.BaseKeeper.SpendableBalanceByDenom(ctx, req)
if err != nil {
return nil, err
}
addr, err := sdk.AccAddressFromBech32(req.Address)
if err != nil {
return nil, sdkerrors.Wrapf(cosmoserrors.ErrInvalidAddress, "invalid address %s", req.Address)
}
if res.Balance == nil {
return res, nil
}

spendableCoin := k.getSpendableCoin(sdk.UnwrapSDKContext(ctx), addr, *res.Balance)
res.Balance = &spendableCoin

return res, nil
}

func (k BaseKeeperWrapper) getSpendableCoin(ctx sdk.Context, addr sdk.AccAddress, coin sdk.Coin) sdk.Coin {
denom := coin.Denom
frozenCoin := k.ftProvider.GetFrozenBalance(ctx, addr, denom)
spendableAmount := coin.Amount.Sub(frozenCoin.Amount)
if spendableAmount.IsNegative() {
return sdk.NewCoin(denom, sdkmath.ZeroInt())
}

return sdk.NewCoin(denom, spendableAmount)
}
Loading

0 comments on commit acc7151

Please sign in to comment.