Skip to content

Commit

Permalink
feat: add support for transfer entire balance for vesting accounts (#…
Browse files Browse the repository at this point in the history
…7650) (#7676)

* fix spendable bug

* remove GetBalance from expected keeper

---------

Co-authored-by: DimitrisJim <[email protected]>
(cherry picked from commit b0d5778)

Co-authored-by: Gjermund Garaba <[email protected]>
  • Loading branch information
mergify[bot] and gjermundgaraba authored Dec 12, 2024
1 parent 4431ce9 commit d42b23b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 3 deletions.
5 changes: 4 additions & 1 deletion modules/apps/transfer/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ func (k Keeper) sendTransfer(
for _, coin := range coins {
// Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom.
if coin.Amount.Equal(types.UnboundedSpendLimit()) {
coin.Amount = k.bankKeeper.GetBalance(ctx, sender, coin.Denom).Amount
coin.Amount = k.bankKeeper.SpendableCoin(ctx, sender, coin.Denom).Amount
if coin.Amount.IsZero() {
return 0, errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom)
}
}

token, err := k.tokenFromCoin(ctx, coin)
Expand Down
61 changes: 60 additions & 1 deletion modules/apps/transfer/keeper/relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"errors"
"fmt"
"strings"
"time"

sdkmath "cosmossdk.io/math"

"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
Expand Down Expand Up @@ -123,6 +126,62 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
},
nil,
},
{
"successful transfer of entire spendable balance with vesting account",
func() {
// create vesting account
vestingAccPrivKey := secp256k1.GenPrivKey()
vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address())

vestingCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, ibctesting.DefaultCoinAmount))
_, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount(
suite.chainA.SenderAccount.GetAddress(),
vestingAccAddress,
vestingCoins,
suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(),
false,
))
suite.Require().NoError(err)
sender = vestingAccAddress

// transfer some spendable coins to vesting account
transferCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, sdkmath.NewInt(42)))
_, err = suite.chainA.SendMsgs(banktypes.NewMsgSend(suite.chainA.SenderAccount.GetAddress(), vestingAccAddress, transferCoins))
suite.Require().NoError(err)

coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit()))
expEscrowAmounts[0] = transferCoins[0].Amount
},
nil,
},
{
"failure: no spendable coins for vesting account",
func() {
// create vesting account
vestingAccPrivKey := secp256k1.GenPrivKey()
vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address())

vestingCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, ibctesting.DefaultCoinAmount))
_, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount(
suite.chainA.SenderAccount.GetAddress(),
vestingAccAddress,
vestingCoins,
suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(),
false,
))
suite.Require().NoError(err)
sender = vestingAccAddress

// just to prove that the vesting account has a balance (but not spendable)
vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, coins[0].Denom)
suite.Require().Equal(vestingCoins[0].Amount.Int64(), vestingAccBalance.Amount.Int64())
vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress)
suite.Require().Zero(vestinSpendableBalance.AmountOf(coins[0].Denom).Int64())

coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit()))
},
types.ErrInvalidAmount,
},
{
"failure: source channel not found",
func() {
Expand Down Expand Up @@ -233,8 +292,8 @@ func (suite *KeeperTestSuite) TestSendTransfer() {

expPass := tc.expError == nil
if expPass {
suite.Require().NotNil(res)
suite.Require().NoError(err)
suite.Require().NotNil(res)
} else {
suite.Require().Nil(res)
suite.Require().Error(err)
Expand Down
2 changes: 1 addition & 1 deletion modules/apps/transfer/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type BankKeeper interface {
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
HasDenomMetaData(ctx context.Context, denom string) bool
SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata)
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins
}

Expand Down

0 comments on commit d42b23b

Please sign in to comment.