Skip to content

Commit

Permalink
Use frozen coins for bank keeper SpendableBalance. (#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzmitryhil authored Oct 26, 2023
1 parent 6b189ce commit 43f49a9
Show file tree
Hide file tree
Showing 5 changed files with 457 additions and 0 deletions.
219 changes: 219 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,224 @@ 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()
recipient1 := 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,
)
requireT.NoError(err)

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

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

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

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

recipientSpendableBalanceAfterFreezeRes, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient1.String(),
Denom: denom1,
})
requireT.NoError(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,
)
requireT.NoError(err)

recipientSpendableBalanceAfterGlobalFreezeRes, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient1.String(),
Denom: denom1,
})
requireT.NoError(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,
)
requireT.NoError(err)

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

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

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

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

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

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

// check the native denom
recipient2 := chain.GenAccount()
amountToFund := sdkmath.NewInt(100)
chain.FundAccountWithOptions(ctx, t, recipient2, integration.BalancesOptions{
Amount: amountToFund,
})
recipient2SpendableBalance, err := bankClient.SpendableBalanceByDenom(ctx, &banktypes.QuerySpendableBalanceByDenomRequest{
Address: recipient2.String(),
Denom: chain.Chain.ChainSettings.Denom,
})
requireT.NoError(err)
requireT.Equal(amountToFund.String(), recipient2SpendableBalance.Balance.Amount.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
55 changes: 55 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,55 @@ 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 {
res.Balances[i] = k.getSpendableCoin(sdk.UnwrapSDKContext(ctx), addr, res.Balances[i])
}

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 43f49a9

Please sign in to comment.