cosmos-sdk/x/bank/keeper/keeper.go

573 lines
18 KiB
Go
Raw Normal View History

package keeper
import (
2018-03-24 17:12:44 -07:00
"fmt"
"time"
2018-03-24 17:12:44 -07:00
"github.com/tendermint/tendermint/libs/log"
2020-01-30 13:31:16 -08:00
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/cosmos/cosmos-sdk/x/bank/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
)
2018-11-07 06:27:20 -08:00
var _ Keeper = (*BaseKeeper)(nil)
// Keeper defines a module interface that facilitates the transfer of coins
// between accounts.
type Keeper interface {
SendKeeper
2018-11-07 11:46:09 -08:00
DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
}
// BaseKeeper manages transfers between accounts. It implements the Keeper interface.
type BaseKeeper struct {
2018-11-07 06:27:20 -08:00
BaseSendKeeper
2018-04-09 15:10:58 -07:00
ak types.AccountKeeper
paramSpace paramtypes.Subspace
2018-04-09 15:10:58 -07:00
}
func NewBaseKeeper(
cdc codec.Marshaler, storeKey sdk.StoreKey, ak types.AccountKeeper, paramSpace paramtypes.Subspace, blacklistedAddrs map[string]bool,
) BaseKeeper {
ps := paramSpace.WithKeyTable(types.ParamKeyTable())
2018-11-07 11:46:09 -08:00
return BaseKeeper{
2020-01-30 13:31:16 -08:00
BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, ps, blacklistedAddrs),
2018-11-07 11:46:09 -08:00
ak: ak,
paramSpace: ps,
2018-11-07 11:46:09 -08:00
}
2018-04-09 15:10:58 -07:00
}
// DelegateCoins performs delegation by deducting amt coins from an account with
// address addr. For vesting accounts, delegations amounts are tracked for both
2020-01-30 13:31:16 -08:00
// vesting and vested coins. The coins are then transferred from the delegator
// address to a ModuleAccount address. If any of the delegation amounts are negative,
// an error is returned.
func (k BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error {
moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr)
2019-06-28 13:11:27 -07:00
if moduleAcc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
2019-06-28 13:11:27 -07:00
}
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
2019-06-28 13:11:27 -07:00
2020-01-30 13:31:16 -08:00
balances := sdk.NewCoins()
2019-06-28 13:11:27 -07:00
2020-01-30 13:31:16 -08:00
for _, coin := range amt {
balance := k.GetBalance(ctx, delegatorAddr, coin.Denom)
if balance.IsLT(coin) {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds, "failed to delegate; %s < %s", balance, amt,
)
}
balances = balances.Add(balance)
k.SetBalance(ctx, delegatorAddr, balance.Sub(coin))
2019-06-28 13:11:27 -07:00
}
2020-01-30 13:31:16 -08:00
if err := k.trackDelegation(ctx, delegatorAddr, ctx.BlockHeader().Time, balances, amt); err != nil {
return sdkerrors.Wrap(err, "failed to track delegation")
2019-06-28 13:11:27 -07:00
}
2020-01-30 13:31:16 -08:00
_, err := k.AddCoins(ctx, moduleAccAddr, amt)
2019-06-28 13:11:27 -07:00
if err != nil {
return err
}
return nil
}
// UndelegateCoins performs undelegation by crediting amt coins to an account with
// address addr. For vesting accounts, undelegation amounts are tracked for both
2020-01-30 13:31:16 -08:00
// vesting and vested coins. The coins are then transferred from a ModuleAccount
// address to the delegator address. If any of the undelegation amounts are
// negative, an error is returned.
func (k BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error {
moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr)
2019-06-28 13:11:27 -07:00
if moduleAcc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
2019-06-28 13:11:27 -07:00
}
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
2019-06-28 13:11:27 -07:00
2020-01-30 13:31:16 -08:00
_, err := k.SubtractCoins(ctx, moduleAccAddr, amt)
2019-06-28 13:11:27 -07:00
if err != nil {
return err
}
2020-01-30 13:31:16 -08:00
if err := k.trackUndelegation(ctx, delegatorAddr, amt); err != nil {
return sdkerrors.Wrap(err, "failed to track undelegation")
2019-06-28 13:11:27 -07:00
}
2020-01-30 13:31:16 -08:00
_, err = k.AddCoins(ctx, delegatorAddr, amt)
if err != nil {
return err
}
2019-06-28 13:11:27 -07:00
return nil
}
// SendKeeper defines a module interface that facilitates the transfer of coins
// between accounts without the possibility of creating coins.
type SendKeeper interface {
ViewKeeper
2018-11-07 06:27:20 -08:00
InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error)
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error)
2020-01-30 13:31:16 -08:00
SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error
SetBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error
GetSendEnabled(ctx sdk.Context) bool
SetSendEnabled(ctx sdk.Context, enabled bool)
BlacklistedAddr(addr sdk.AccAddress) bool
}
var _ SendKeeper = (*BaseSendKeeper)(nil)
// BaseSendKeeper only allows transfers between accounts without the possibility of
// creating coins. It implements the SendKeeper interface.
type BaseSendKeeper struct {
2018-11-07 06:27:20 -08:00
BaseViewKeeper
2018-04-09 15:10:58 -07:00
cdc codec.Marshaler
ak types.AccountKeeper
2020-01-30 13:31:16 -08:00
storeKey sdk.StoreKey
paramSpace paramtypes.Subspace
// list of addresses that are restricted from receiving transactions
blacklistedAddrs map[string]bool
2018-04-09 15:10:58 -07:00
}
func NewBaseSendKeeper(
cdc codec.Marshaler, storeKey sdk.StoreKey, ak types.AccountKeeper, paramSpace paramtypes.Subspace, blacklistedAddrs map[string]bool,
) BaseSendKeeper {
2018-11-07 11:46:09 -08:00
return BaseSendKeeper{
2020-01-30 13:31:16 -08:00
BaseViewKeeper: NewBaseViewKeeper(cdc, storeKey, ak),
cdc: cdc,
ak: ak,
2020-01-30 13:31:16 -08:00
storeKey: storeKey,
paramSpace: paramSpace,
blacklistedAddrs: blacklistedAddrs,
2018-11-07 11:46:09 -08:00
}
2018-04-09 15:10:58 -07:00
}
2020-01-30 13:31:16 -08:00
// InputOutputCoins performs multi-send functionality. It accepts a series of
// inputs that correspond to a series of outputs. It returns an error if the
// inputs and outputs don't lineup or if any single transfer of tokens fails.
func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error {
// Safety check ensuring that when sending coins the keeper must maintain the
// Check supply invariant and validity of Coins.
if err := types.ValidateInputsOutputs(inputs, outputs); err != nil {
return err
}
for _, in := range inputs {
2020-01-30 13:31:16 -08:00
_, err := k.SubtractCoins(ctx, in.Address, in.Coins)
if err != nil {
return err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(types.AttributeKeySender, in.Address.String()),
),
)
}
for _, out := range outputs {
2020-01-30 13:31:16 -08:00
_, err := k.AddCoins(ctx, out.Address, out.Coins)
if err != nil {
return err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeTransfer,
sdk.NewAttribute(types.AttributeKeyRecipient, out.Address.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()),
),
)
}
return nil
}
2020-01-30 13:31:16 -08:00
// SendCoins transfers amt coins from a sending account to a receiving account.
// An error is returned upon failure.
func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error {
Merge PR #4541: Events Tracking / Tendermint v0.32.0 Update * Update Tendermint to v0.32.0-dev0 * Initial refactor of tags * Update event types and add unit tests * Refactor context * Update module manager * Update result godoc * Implement ToABCIEvents * Update BaseApp * Minor cleanup * Fix typo * Update x/bank message handler * Update x/bank keeper * Update x/bank * Update x/bank events docs * Update x/crisis module events * Reset context with events on each message exec * Update x/distribution events and docs * Update BaseApp to not set empty events manually * Implement simple event manager * Update module manager * Update modules to use event manager * Update x/gov module to use events * Update events docs * Update gov queries and crisis app module * Update bank keeper * Add events to minting begin blocker * Update modules to use types/events.go * Cleanup x/mint * Update x/staking events * Update x/staking events * Update events to have sender part of message.sender * Fix build * Fix module unit tests * Add pending log entry * Update deps * Update x/crisis/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/bank/internal/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/distribution/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/internal/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/gov/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/gov/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/abci.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/abci.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Upgrade TM to v0.32.0-dev1 * Update events as strings * Update Tendermint to v0.32.0-dev2 * Fix BaseApp unit tests * Fix unit tests * Bump tendermint version to v0.32.0 * typos
2019-06-26 09:03:25 -07:00
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeTransfer,
sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()),
Merge PR #4541: Events Tracking / Tendermint v0.32.0 Update * Update Tendermint to v0.32.0-dev0 * Initial refactor of tags * Update event types and add unit tests * Refactor context * Update module manager * Update result godoc * Implement ToABCIEvents * Update BaseApp * Minor cleanup * Fix typo * Update x/bank message handler * Update x/bank keeper * Update x/bank * Update x/bank events docs * Update x/crisis module events * Reset context with events on each message exec * Update x/distribution events and docs * Update BaseApp to not set empty events manually * Implement simple event manager * Update module manager * Update modules to use event manager * Update x/gov module to use events * Update events docs * Update gov queries and crisis app module * Update bank keeper * Add events to minting begin blocker * Update modules to use types/events.go * Cleanup x/mint * Update x/staking events * Update x/staking events * Update events to have sender part of message.sender * Fix build * Fix module unit tests * Add pending log entry * Update deps * Update x/crisis/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/bank/internal/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/distribution/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/internal/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/types/events.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/gov/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/gov/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/abci.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/mint/abci.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/slashing/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update x/staking/handler.go Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Upgrade TM to v0.32.0-dev1 * Update events as strings * Update Tendermint to v0.32.0-dev2 * Fix BaseApp unit tests * Fix unit tests * Bump tendermint version to v0.32.0 * typos
2019-06-26 09:03:25 -07:00
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(types.AttributeKeySender, fromAddr.String()),
),
})
2020-01-30 13:31:16 -08:00
_, err := k.SubtractCoins(ctx, fromAddr, amt)
if err != nil {
return err
}
2020-01-30 13:31:16 -08:00
_, err = k.AddCoins(ctx, toAddr, amt)
if err != nil {
return err
}
// Create account if recipient does not exist.
//
// NOTE: This should ultimately be removed in favor a more flexible approach
// such as delegated fee messages.
acc := k.ak.GetAccount(ctx, toAddr)
if acc == nil {
k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr))
}
2018-04-18 21:49:24 -07:00
return nil
}
2020-01-30 13:31:16 -08:00
// SubtractCoins removes amt coins the account by the given address. An error is
// returned if the resulting balance is negative or the initial amount is invalid.
func (k BaseSendKeeper) SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) {
if !amt.IsValid() {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
2020-01-30 13:31:16 -08:00
resultCoins := sdk.NewCoins()
lockedCoins := k.LockedCoins(ctx, addr)
2020-01-30 13:31:16 -08:00
for _, coin := range amt {
balance := k.GetBalance(ctx, addr, coin.Denom)
locked := sdk.NewCoin(coin.Denom, lockedCoins.AmountOf(coin.Denom))
spendable := balance.Sub(locked)
2020-01-30 13:31:16 -08:00
_, hasNeg := sdk.Coins{spendable}.SafeSub(sdk.Coins{coin})
if hasNeg {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%s < %s", spendable, coin)
}
newBalance := balance.Sub(coin)
resultCoins = resultCoins.Add(newBalance)
2020-01-30 13:31:16 -08:00
k.SetBalance(ctx, addr, newBalance)
}
2020-01-30 13:31:16 -08:00
return resultCoins, nil
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
// AddCoins adds amt to the account balance given by the provided address. An
// error is returned if the initial amount is invalid or if any resulting new
// balance is negative.
func (k BaseSendKeeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) {
if !amt.IsValid() {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
2020-01-30 13:31:16 -08:00
var resultCoins sdk.Coins
2020-01-30 13:31:16 -08:00
for _, coin := range amt {
balance := k.GetBalance(ctx, addr, coin.Denom)
newBalance := balance.Add(coin)
resultCoins = resultCoins.Add(newBalance)
k.SetBalance(ctx, addr, newBalance)
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
return resultCoins, nil
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
// ClearBalances removes all balances for a given account by address.
func (k BaseSendKeeper) ClearBalances(ctx sdk.Context, addr sdk.AccAddress) {
keys := [][]byte{}
k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool {
keys = append(keys, []byte(balance.Denom))
return false
})
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
for _, key := range keys {
accountStore.Delete(key)
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
}
2018-04-18 21:49:24 -07:00
2020-01-30 13:31:16 -08:00
// SetBalances sets the balance (multiple coins) for an account by address. It will
// clear out all balances prior to setting the new coins as to set existing balances
// to zero if they don't exist in amt. An error is returned upon failure.
func (k BaseSendKeeper) SetBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error {
k.ClearBalances(ctx, addr)
for _, balance := range balances {
err := k.SetBalance(ctx, addr, balance)
if err != nil {
return err
}
}
2020-01-30 13:31:16 -08:00
return nil
}
// SetBalance sets the coin balance for an account by address.
func (k BaseSendKeeper) SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error {
if !balance.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String())
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
bz := k.cdc.MustMarshalBinaryBare(&balance)
2020-01-30 13:31:16 -08:00
accountStore.Set([]byte(balance.Denom), bz)
return nil
2018-04-18 21:49:24 -07:00
}
// GetSendEnabled returns the current SendEnabled
2020-01-30 13:31:16 -08:00
func (k BaseSendKeeper) GetSendEnabled(ctx sdk.Context) bool {
var enabled bool
2020-01-30 13:31:16 -08:00
k.paramSpace.Get(ctx, types.ParamStoreKeySendEnabled, &enabled)
return enabled
}
2019-01-02 13:19:48 -08:00
// SetSendEnabled sets the send enabled
2020-01-30 13:31:16 -08:00
func (k BaseSendKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) {
k.paramSpace.Set(ctx, types.ParamStoreKeySendEnabled, &enabled)
}
// BlacklistedAddr checks if a given address is blacklisted (i.e restricted from
// receiving funds)
2020-01-30 13:31:16 -08:00
func (k BaseSendKeeper) BlacklistedAddr(addr sdk.AccAddress) bool {
return k.blacklistedAddrs[addr.String()]
}
var _ ViewKeeper = (*BaseViewKeeper)(nil)
2018-04-18 21:49:24 -07:00
// ViewKeeper defines a module interface that facilitates read only access to
// account balances.
type ViewKeeper interface {
2020-01-30 13:31:16 -08:00
ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) error
HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool
GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
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
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))
}
// BaseViewKeeper implements a read only keeper implementation of ViewKeeper.
type BaseViewKeeper struct {
cdc codec.Marshaler
2020-01-30 13:31:16 -08:00
storeKey sdk.StoreKey
ak types.AccountKeeper
}
// NewBaseViewKeeper returns a new BaseViewKeeper.
func NewBaseViewKeeper(cdc codec.Marshaler, storeKey sdk.StoreKey, ak types.AccountKeeper) BaseViewKeeper {
2020-01-30 13:31:16 -08:00
return BaseViewKeeper{
cdc: cdc,
storeKey: storeKey,
ak: ak,
}
}
// Logger returns a module-specific logger.
2020-01-30 13:31:16 -08:00
func (k BaseViewKeeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
2020-01-30 13:31:16 -08:00
// HasBalance returns whether or not an account has at least amt balance.
func (k BaseViewKeeper) HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool {
return k.GetBalance(ctx, addr, amt.Denom).IsGTE(amt)
}
// GetAllBalances returns all the account balances for the given account address.
func (k BaseViewKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
balances := sdk.NewCoins()
k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool {
balances = balances.Add(balance)
return false
})
return balances.Sort()
}
// GetBalance returns the balance of a specific denomination for a given account
// by address.
func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
bz := accountStore.Get([]byte(denom))
if bz == nil {
return sdk.NewCoin(denom, sdk.ZeroInt())
}
var balance sdk.Coin
k.cdc.MustUnmarshalBinaryBare(bz, &balance)
return balance
}
// IterateAccountBalances iterates over the balances of a single account and
// provides the token balance to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) {
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
iterator := accountStore.Iterator(nil, nil)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var balance sdk.Coin
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &balance)
if cb(balance) {
break
}
}
}
// IterateAllBalances iterates over all the balances of all accounts and
// denominations that are provided to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseViewKeeper) IterateAllBalances(ctx sdk.Context, cb func(sdk.AccAddress, sdk.Coin) bool) {
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
iterator := balancesStore.Iterator(nil, nil)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
address := types.AddressFromBalancesStore(iterator.Key())
var balance sdk.Coin
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &balance)
if cb(address, balance) {
break
}
}
}
// LockedCoins returns all the coins that are not spendable (i.e. locked) for an
// account by address. For standard accounts, the result will always be no coins.
// For vesting accounts, LockedCoins is delegated to the concrete vesting account
// type.
func (k BaseViewKeeper) LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
acc := k.ak.GetAccount(ctx, addr)
if acc != nil {
vacc, ok := acc.(vestexported.VestingAccount)
if ok {
return vacc.LockedCoins(ctx.BlockTime())
}
}
return sdk.NewCoins()
}
// SpendableCoins returns the total balances of spendable coins for an account
// by address. If the account has no spendable coins, an empty Coins slice is
// returned.
func (k BaseViewKeeper) SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
balances := k.GetAllBalances(ctx, addr)
locked := k.LockedCoins(ctx, addr)
spendable, hasNeg := balances.SafeSub(locked)
if hasNeg {
return sdk.NewCoins()
2018-04-18 21:49:24 -07:00
}
2020-01-30 13:31:16 -08:00
return spendable
}
2018-04-18 21:49:24 -07:00
2020-01-30 13:31:16 -08:00
// ValidateBalance validates all balances for a given account address returning
// an error if any balance is invalid. It will check for vesting account types
// and validate the balances against the original vesting balances.
//
// CONTRACT: ValidateBalance should only be called upon genesis state. In the
// case of vesting accounts, balances may change in a valid manner that would
// otherwise yield an error from this call.
func (k BaseViewKeeper) ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) error {
acc := k.ak.GetAccount(ctx, addr)
if acc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
}
balances := k.GetAllBalances(ctx, addr)
if !balances.IsValid() {
return fmt.Errorf("account balance of %s is invalid", balances)
}
vacc, ok := acc.(vestexported.VestingAccount)
if ok {
ogv := vacc.GetOriginalVesting()
if ogv.IsAnyGT(balances) {
return fmt.Errorf("vesting amount %s cannot be greater than total amount %s", ogv, balances)
}
}
return nil
}
2020-01-30 13:31:16 -08:00
func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, blockTime time.Time, balance, amt sdk.Coins) error {
acc := k.ak.GetAccount(ctx, addr)
if acc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
}
vacc, ok := acc.(vestexported.VestingAccount)
if ok {
// TODO: return error on account.TrackDelegation
2020-01-30 13:31:16 -08:00
vacc.TrackDelegation(blockTime, balance, amt)
}
2020-01-30 13:31:16 -08:00
return nil
}
2020-01-30 13:31:16 -08:00
func (k BaseKeeper) trackUndelegation(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
acc := k.ak.GetAccount(ctx, addr)
if acc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
}
vacc, ok := acc.(vestexported.VestingAccount)
if ok {
// TODO: return error on account.TrackUndelegation
vacc.TrackUndelegation(amt)
}
2020-01-30 13:31:16 -08:00
return nil
}