github.com/Finschia/finschia-sdk@v0.48.1/x/bank/keeper/view.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 "github.com/Finschia/ostracon/libs/log" 7 8 "github.com/Finschia/finschia-sdk/codec" 9 "github.com/Finschia/finschia-sdk/store/prefix" 10 sdk "github.com/Finschia/finschia-sdk/types" 11 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 12 vestexported "github.com/Finschia/finschia-sdk/x/auth/vesting/exported" 13 "github.com/Finschia/finschia-sdk/x/bank/types" 14 ) 15 16 var _ ViewKeeper = (*BaseViewKeeper)(nil) 17 18 // ViewKeeper defines a module interface that facilitates read only access to 19 // account balances. 20 type ViewKeeper interface { 21 ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) error 22 HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool 23 24 GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 25 GetAccountsBalances(ctx sdk.Context) []types.Balance 26 GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin 27 LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 28 SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins 29 30 IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) 31 IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) 32 } 33 34 // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. 35 type BaseViewKeeper struct { 36 cdc codec.BinaryCodec 37 storeKey sdk.StoreKey 38 ak types.AccountKeeper 39 } 40 41 // NewBaseViewKeeper returns a new BaseViewKeeper. 42 func NewBaseViewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, ak types.AccountKeeper) BaseViewKeeper { 43 return BaseViewKeeper{ 44 cdc: cdc, 45 storeKey: storeKey, 46 ak: ak, 47 } 48 } 49 50 // Logger returns a module-specific logger. 51 func (k BaseViewKeeper) Logger(ctx sdk.Context) log.Logger { 52 return ctx.Logger().With("module", "x/"+types.ModuleName) 53 } 54 55 // HasBalance returns whether or not an account has at least amt balance. 56 func (k BaseViewKeeper) HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool { 57 return k.GetBalance(ctx, addr, amt.Denom).IsGTE(amt) 58 } 59 60 // GetAllBalances returns all the account balances for the given account address. 61 func (k BaseViewKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { 62 balances := sdk.NewCoins() 63 k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool { 64 balances = balances.Add(balance) 65 return false 66 }) 67 68 return balances.Sort() 69 } 70 71 // GetAccountsBalances returns all the accounts balances from the store. 72 func (k BaseViewKeeper) GetAccountsBalances(ctx sdk.Context) []types.Balance { 73 balances := make([]types.Balance, 0) 74 mapAddressToBalancesIdx := make(map[string]int) 75 76 k.IterateAllBalances(ctx, func(addr sdk.AccAddress, balance sdk.Coin) bool { 77 idx, ok := mapAddressToBalancesIdx[addr.String()] 78 if ok { 79 // address is already on the set of accounts balances 80 balances[idx].Coins = balances[idx].Coins.Add(balance) 81 balances[idx].Coins.Sort() 82 return false 83 } 84 85 accountBalance := types.Balance{ 86 Address: addr.String(), 87 Coins: sdk.NewCoins(balance), 88 } 89 balances = append(balances, accountBalance) 90 mapAddressToBalancesIdx[addr.String()] = len(balances) - 1 91 return false 92 }) 93 94 return balances 95 } 96 97 // GetBalance returns the balance of a specific denomination for a given account 98 // by address. 99 func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { 100 accountStore := k.getAccountStore(ctx, addr) 101 102 bz := accountStore.Get([]byte(denom)) 103 if bz == nil { 104 return sdk.NewCoin(denom, sdk.ZeroInt()) 105 } 106 107 var balance sdk.Coin 108 k.cdc.MustUnmarshal(bz, &balance) 109 110 return balance 111 } 112 113 // IterateAccountBalances iterates over the balances of a single account and 114 // provides the token balance to a callback. If true is returned from the 115 // callback, iteration is halted. 116 func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) { 117 accountStore := k.getAccountStore(ctx, addr) 118 119 iterator := accountStore.Iterator(nil, nil) 120 defer iterator.Close() 121 122 for ; iterator.Valid(); iterator.Next() { 123 var balance sdk.Coin 124 k.cdc.MustUnmarshal(iterator.Value(), &balance) 125 126 if cb(balance) { 127 break 128 } 129 } 130 } 131 132 // IterateAllBalances iterates over all the balances of all accounts and 133 // denominations that are provided to a callback. If true is returned from the 134 // callback, iteration is halted. 135 func (k BaseViewKeeper) IterateAllBalances(ctx sdk.Context, cb func(sdk.AccAddress, sdk.Coin) bool) { 136 store := ctx.KVStore(k.storeKey) 137 balancesStore := prefix.NewStore(store, types.BalancesPrefix) 138 139 iterator := balancesStore.Iterator(nil, nil) 140 defer iterator.Close() 141 142 for ; iterator.Valid(); iterator.Next() { 143 address, err := types.AddressFromBalancesStore(iterator.Key()) 144 if err != nil { 145 k.Logger(ctx).With("key", iterator.Key(), "err", err).Error("failed to get address from balances store") 146 // TODO: revisit, for now, panic here to keep same behavior as in 0.42 147 // ref: https://github.com/cosmos/cosmos-sdk/issues/7409 148 panic(err) 149 } 150 151 var balance sdk.Coin 152 k.cdc.MustUnmarshal(iterator.Value(), &balance) 153 154 if cb(address, balance) { 155 break 156 } 157 } 158 } 159 160 // LockedCoins returns all the coins that are not spendable (i.e. locked) for an 161 // account by address. For standard accounts, the result will always be no coins. 162 // For vesting accounts, LockedCoins is delegated to the concrete vesting account 163 // type. 164 func (k BaseViewKeeper) LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { 165 acc := k.ak.GetAccount(ctx, addr) 166 if acc != nil { 167 vacc, ok := acc.(vestexported.VestingAccount) 168 if ok { 169 return vacc.LockedCoins(ctx.BlockTime()) 170 } 171 } 172 173 return sdk.NewCoins() 174 } 175 176 // SpendableCoins returns the total balances of spendable coins for an account 177 // by address. If the account has no spendable coins, an empty Coins slice is 178 // returned. 179 func (k BaseViewKeeper) SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { 180 spendable, _ := k.spendableCoins(ctx, addr) 181 return spendable 182 } 183 184 // spendableCoins returns the coins the given address can spend alongside the total amount of coins it holds. 185 // It exists for gas efficiency, in order to avoid to have to get balance multiple times. 186 func (k BaseViewKeeper) spendableCoins(ctx sdk.Context, addr sdk.AccAddress) (spendable, total sdk.Coins) { 187 total = k.GetAllBalances(ctx, addr) 188 locked := k.LockedCoins(ctx, addr) 189 190 spendable, hasNeg := total.SafeSub(locked) 191 if hasNeg { 192 spendable = sdk.NewCoins() 193 return 194 } 195 196 return 197 } 198 199 // ValidateBalance validates all balances for a given account address returning 200 // an error if any balance is invalid. It will check for vesting account types 201 // and validate the balances against the original vesting balances. 202 // 203 // CONTRACT: ValidateBalance should only be called upon genesis state. In the 204 // case of vesting accounts, balances may change in a valid manner that would 205 // otherwise yield an error from this call. 206 func (k BaseViewKeeper) ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) error { 207 acc := k.ak.GetAccount(ctx, addr) 208 if acc == nil { 209 return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr) 210 } 211 212 balances := k.GetAllBalances(ctx, addr) 213 if !balances.IsValid() { 214 return fmt.Errorf("account balance of %s is invalid", balances) 215 } 216 217 vacc, ok := acc.(vestexported.VestingAccount) 218 if ok { 219 ogv := vacc.GetOriginalVesting() 220 if ogv.IsAnyGT(balances) { 221 return fmt.Errorf("vesting amount %s cannot be greater than total amount %s", ogv, balances) 222 } 223 } 224 225 return nil 226 } 227 228 // getAccountStore gets the account store of the given address. 229 func (k BaseViewKeeper) getAccountStore(ctx sdk.Context, addr sdk.AccAddress) prefix.Store { 230 store := ctx.KVStore(k.storeKey) 231 232 return prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr.Bytes())) 233 }