Skip to content

Commit 7e7c17b

Browse files
gjermundgarabaDimitrisJim
authored andcommitted
feat: add support for transfer entire balance for vesting accounts (#7650)
* fix spendable bug * remove GetBalance from expected keeper --------- Co-authored-by: DimitrisJim <d.f.hilliard@gmail.com> (cherry picked from commit b0d5778)
1 parent 4431ce9 commit 7e7c17b

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

modules/apps/transfer/keeper/relay.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ func (k Keeper) sendTransfer(
100100
for _, coin := range coins {
101101
// Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom.
102102
if coin.Amount.Equal(types.UnboundedSpendLimit()) {
103-
coin.Amount = k.bankKeeper.GetBalance(ctx, sender, coin.Denom).Amount
103+
coin.Amount = k.bankKeeper.SpendableCoin(ctx, sender, coin.Denom).Amount
104+
if coin.Amount.IsZero() {
105+
return 0, errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom)
106+
}
104107
}
105108

106109
token, err := k.tokenFromCoin(ctx, coin)

modules/apps/transfer/keeper/relay_test.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"errors"
55
"fmt"
66
"strings"
7+
"time"
78

89
sdkmath "cosmossdk.io/math"
910

11+
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
1012
sdk "github.com/cosmos/cosmos-sdk/types"
1113
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
14+
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
1215
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
1316
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
1417
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
@@ -123,6 +126,62 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
123126
},
124127
nil,
125128
},
129+
{
130+
"successful transfer of entire spendable balance with vesting account",
131+
func() {
132+
// create vesting account
133+
vestingAccPrivKey := secp256k1.GenPrivKey()
134+
vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address())
135+
136+
vestingCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, ibctesting.DefaultCoinAmount))
137+
_, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount(
138+
suite.chainA.SenderAccount.GetAddress(),
139+
vestingAccAddress,
140+
vestingCoins,
141+
suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(),
142+
false,
143+
))
144+
suite.Require().NoError(err)
145+
sender = vestingAccAddress
146+
147+
// transfer some spendable coins to vesting account
148+
transferCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, sdkmath.NewInt(42)))
149+
_, err = suite.chainA.SendMsgs(banktypes.NewMsgSend(suite.chainA.SenderAccount.GetAddress(), vestingAccAddress, transferCoins))
150+
suite.Require().NoError(err)
151+
152+
coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit()))
153+
expEscrowAmounts[0] = transferCoins[0].Amount
154+
},
155+
nil,
156+
},
157+
{
158+
"failure: no spendable coins for vesting account",
159+
func() {
160+
// create vesting account
161+
vestingAccPrivKey := secp256k1.GenPrivKey()
162+
vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address())
163+
164+
vestingCoins := sdk.NewCoins(sdk.NewCoin(coins[0].Denom, ibctesting.DefaultCoinAmount))
165+
_, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount(
166+
suite.chainA.SenderAccount.GetAddress(),
167+
vestingAccAddress,
168+
vestingCoins,
169+
suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(),
170+
false,
171+
))
172+
suite.Require().NoError(err)
173+
sender = vestingAccAddress
174+
175+
// just to prove that the vesting account has a balance (but not spendable)
176+
vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, coins[0].Denom)
177+
suite.Require().Equal(vestingCoins[0].Amount.Int64(), vestingAccBalance.Amount.Int64())
178+
vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress)
179+
suite.Require().Zero(vestinSpendableBalance.AmountOf(coins[0].Denom).Int64())
180+
181+
coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit()))
182+
},
183+
types.ErrInvalidAmount,
184+
},
126185
{
127186
"failure: source channel not found",
128187
func() {
@@ -233,8 +292,8 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
233292

234293
expPass := tc.expError == nil
235294
if expPass {
236-
suite.Require().NotNil(res)
237295
suite.Require().NoError(err)
296+
suite.Require().NotNil(res)
238297
} else {
239298
suite.Require().Nil(res)
240299
suite.Require().Error(err)

modules/apps/transfer/types/expected_keepers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type BankKeeper interface {
3030
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
3131
HasDenomMetaData(ctx context.Context, denom string) bool
3232
SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata)
33-
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
33+
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
3434
GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins
3535
}
3636

0 commit comments

Comments
 (0)