diff --git a/CHANGELOG.md b/CHANGELOG.md index cf848506e06..0654f106245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* (x/bank) [\#12593](https://github.com/cosmos/cosmos-sdk/pull/12593) Add `SpendableCoin` method to `BaseViewKeeper` * (x/slashing) [#12581](https://github.com/cosmos/cosmos-sdk/pull/12581) Remove `x/slashing` legacy querier. * (types) [\#12355](https://github.com/cosmos/cosmos-sdk/pull/12355) Remove the compile-time `types.DBbackend` variable. Removes usage of the same in server/util.go * (x/gov) [#12368](https://github.com/cosmos/cosmos-sdk/pull/12369) Gov keeper is now passed by reference instead of copy to make post-construction mutation of Hooks and Proposal Handlers possible at a framework level. diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 8d71ecff230..ddeef73ca7e 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -708,10 +708,12 @@ func (suite *IntegrationTestSuite) TestSpendableCoins() { suite.Require().NoError(testutil.FundAccount(app.BankKeeper, ctx, addr2, origCoins)) suite.Require().Equal(origCoins, app.BankKeeper.SpendableCoins(ctx, addr2)) + suite.Require().Equal(origCoins[0], app.BankKeeper.SpendableCoin(ctx, addr2, "stake")) ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) suite.Require().NoError(app.BankKeeper.DelegateCoins(ctx, addr2, addrModule, delCoins)) suite.Require().Equal(origCoins.Sub(delCoins...), app.BankKeeper.SpendableCoins(ctx, addr1)) + suite.Require().Equal(origCoins.Sub(delCoins...)[0], app.BankKeeper.SpendableCoin(ctx, addr1, "stake")) } func (suite *IntegrationTestSuite) TestVestingAccountSend() { diff --git a/x/bank/keeper/view.go b/x/bank/keeper/view.go index 5fc3446c2da..35e64082376 100644 --- a/x/bank/keeper/view.go +++ b/x/bank/keeper/view.go @@ -27,6 +27,7 @@ type ViewKeeper interface { GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) @@ -187,6 +188,15 @@ func (k BaseViewKeeper) SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk return spendable } +// SpendableCoin returns the balance of specific denomination of spendable coins +// for an account by address. If the account has no spendable coin, a zero Coin +// is returned. +func (k BaseViewKeeper) SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { + balance := k.GetBalance(ctx, addr, denom) + locked := k.LockedCoins(ctx, addr) + return balance.SubAmount(locked.AmountOf(denom)) +} + // spendableCoins returns the coins the given address can spend alongside the total amount of coins it holds. // It exists for gas efficiency, in order to avoid to have to get balance multiple times. func (k BaseViewKeeper) spendableCoins(ctx sdk.Context, addr sdk.AccAddress) (spendable, total sdk.Coins) { diff --git a/x/bank/spec/02_keepers.md b/x/bank/spec/02_keepers.md index 7bca8fe12b8..ef65aaebfc0 100644 --- a/x/bank/spec/02_keepers.md +++ b/x/bank/spec/02_keepers.md @@ -140,6 +140,7 @@ type ViewKeeper interface { GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool))