244 lines
8.1 KiB
Go
244 lines
8.1 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
)
|
|
|
|
var _ ViewKeeper = (*BaseViewKeeper)(nil)
|
|
|
|
// ViewKeeper defines a module interface that facilitates read only access to
|
|
// account balances.
|
|
type ViewKeeper interface {
|
|
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
|
|
GetAccountsBalances(ctx sdk.Context) []types.Balance
|
|
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.BinaryCodec
|
|
storeKey storetypes.StoreKey
|
|
ak types.AccountKeeper
|
|
}
|
|
|
|
// NewBaseViewKeeper returns a new BaseViewKeeper.
|
|
func NewBaseViewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ak types.AccountKeeper) BaseViewKeeper {
|
|
return BaseViewKeeper{
|
|
cdc: cdc,
|
|
storeKey: storeKey,
|
|
ak: ak,
|
|
}
|
|
}
|
|
|
|
// Logger returns a module-specific logger.
|
|
func (k BaseViewKeeper) Logger(ctx sdk.Context) log.Logger {
|
|
return ctx.Logger().With("module", "x/"+types.ModuleName)
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// GetAccountsBalances returns all the accounts balances from the store.
|
|
func (k BaseViewKeeper) GetAccountsBalances(ctx sdk.Context) []types.Balance {
|
|
balances := make([]types.Balance, 0)
|
|
mapAddressToBalancesIdx := make(map[string]int)
|
|
|
|
k.IterateAllBalances(ctx, func(addr sdk.AccAddress, balance sdk.Coin) bool {
|
|
idx, ok := mapAddressToBalancesIdx[addr.String()]
|
|
if ok {
|
|
// address is already on the set of accounts balances
|
|
balances[idx].Coins = balances[idx].Coins.Add(balance)
|
|
balances[idx].Coins.Sort()
|
|
return false
|
|
}
|
|
|
|
accountBalance := types.Balance{
|
|
Address: addr.String(),
|
|
Coins: sdk.NewCoins(balance),
|
|
}
|
|
balances = append(balances, accountBalance)
|
|
mapAddressToBalancesIdx[addr.String()] = len(balances) - 1
|
|
return false
|
|
})
|
|
|
|
return balances
|
|
}
|
|
|
|
// 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 {
|
|
accountStore := k.getAccountStore(ctx, addr)
|
|
amount := sdk.ZeroInt()
|
|
bz := accountStore.Get([]byte(denom))
|
|
if bz == nil {
|
|
return sdk.NewCoin(denom, amount)
|
|
}
|
|
|
|
if err := amount.Unmarshal(bz); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return sdk.NewCoin(denom, amount)
|
|
}
|
|
|
|
// 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) {
|
|
accountStore := k.getAccountStore(ctx, addr)
|
|
|
|
iterator := accountStore.Iterator(nil, nil)
|
|
defer iterator.Close()
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var amount sdk.Int
|
|
if err := amount.Unmarshal(iterator.Value()); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if cb(sdk.NewCoin(string(iterator.Key()), amount)) {
|
|
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, denom, err := types.AddressAndDenomFromBalancesStore(iterator.Key())
|
|
if err != nil {
|
|
k.Logger(ctx).With("key", iterator.Key(), "err", err).Error("failed to get address from balances store")
|
|
// TODO: revisit, for now, panic here to keep same behavior as in 0.42
|
|
// ref: https://github.com/cosmos/cosmos-sdk/issues/7409
|
|
panic(err)
|
|
}
|
|
|
|
var amount sdk.Int
|
|
if err := amount.Unmarshal(iterator.Value()); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if cb(address, sdk.NewCoin(denom, amount)) {
|
|
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.(types.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 {
|
|
spendable, _ := k.spendableCoins(ctx, addr)
|
|
return spendable
|
|
}
|
|
|
|
// 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) {
|
|
total = k.GetAllBalances(ctx, addr)
|
|
locked := k.LockedCoins(ctx, addr)
|
|
|
|
spendable, hasNeg := total.SafeSub(locked)
|
|
if hasNeg {
|
|
spendable = sdk.NewCoins()
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// 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.(types.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
|
|
}
|
|
|
|
// getAccountStore gets the account store of the given address.
|
|
func (k BaseViewKeeper) getAccountStore(ctx sdk.Context, addr sdk.AccAddress) prefix.Store {
|
|
store := ctx.KVStore(k.storeKey)
|
|
return prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr))
|
|
}
|
|
|
|
// getDenomAddressPrefixStore returns a prefix store that acts as a reverse index
|
|
// between a denomination and account balance for that denomination.
|
|
func (k BaseViewKeeper) getDenomAddressPrefixStore(ctx sdk.Context, denom string) prefix.Store {
|
|
return prefix.NewStore(ctx.KVStore(k.storeKey), types.CreateDenomAddressPrefix(denom))
|
|
}
|