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

401 lines
13 KiB
Go

package keeper
import (
"fmt"
"time"
"github.com/gogo/protobuf/proto"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/cosmos/cosmos-sdk/x/bank/exported"
"github.com/cosmos/cosmos-sdk/x/bank/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
)
var _ Keeper = (*BaseKeeper)(nil)
// Keeper defines a module interface that facilitates the transfer of coins
// between accounts.
type Keeper interface {
SendKeeper
GetSupply(ctx sdk.Context) exported.SupplyI
SetSupply(ctx sdk.Context, supply exported.SupplyI)
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
MarshalSupply(supplyI exported.SupplyI) ([]byte, error)
UnmarshalSupply(bz []byte) (exported.SupplyI, error)
MarshalSupplyJSON(supply exported.SupplyI) ([]byte, error)
UnmarshalSupplyJSON(bz []byte) (exported.SupplyI, error)
types.QueryServer
}
// BaseKeeper manages transfers between accounts. It implements the Keeper interface.
type BaseKeeper struct {
BaseSendKeeper
ak types.AccountKeeper
cdc codec.Marshaler
storeKey sdk.StoreKey
paramSpace paramtypes.Subspace
}
func NewBaseKeeper(
cdc codec.Marshaler, storeKey sdk.StoreKey, ak types.AccountKeeper, paramSpace paramtypes.Subspace,
blockedAddrs map[string]bool,
) BaseKeeper {
// set KeyTable if it has not already been set
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
return BaseKeeper{
BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, paramSpace, blockedAddrs),
ak: ak,
cdc: cdc,
storeKey: storeKey,
paramSpace: paramSpace,
}
}
// DelegateCoins performs delegation by deducting amt coins from an account with
// address addr. For vesting accounts, delegations amounts are tracked for both
// 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)
if moduleAcc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
}
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
balances := sdk.NewCoins()
for _, coin := range amt {
balance := k.GetBalance(ctx, delegatorAddr, coin.Denom)
if balance.IsLT(coin) {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds, "failed to delegate; %s is smaller than %s", balance, amt,
)
}
balances = balances.Add(balance)
err := k.SetBalance(ctx, delegatorAddr, balance.Sub(coin))
if err != nil {
return err
}
}
if err := k.trackDelegation(ctx, delegatorAddr, ctx.BlockHeader().Time, balances, amt); err != nil {
return sdkerrors.Wrap(err, "failed to track delegation")
}
_, err := k.AddCoins(ctx, moduleAccAddr, amt)
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
// 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)
if moduleAcc == nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
}
if !amt.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
}
_, err := k.SubtractCoins(ctx, moduleAccAddr, amt)
if err != nil {
return err
}
if err := k.trackUndelegation(ctx, delegatorAddr, amt); err != nil {
return sdkerrors.Wrap(err, "failed to track undelegation")
}
_, err = k.AddCoins(ctx, delegatorAddr, amt)
if err != nil {
return err
}
return nil
}
// GetSupply retrieves the Supply from store
func (k BaseKeeper) GetSupply(ctx sdk.Context) exported.SupplyI {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.SupplyKey)
if bz == nil {
panic("stored supply should not have been nil")
}
supply, err := k.UnmarshalSupply(bz)
if err != nil {
panic(err)
}
return supply
}
// SetSupply sets the Supply to store
func (k BaseKeeper) SetSupply(ctx sdk.Context, supply exported.SupplyI) {
store := ctx.KVStore(k.storeKey)
bz, err := k.MarshalSupply(supply)
if err != nil {
panic(err)
}
store.Set(types.SupplyKey, bz)
}
// SendCoinsFromModuleToAccount transfers coins from a ModuleAccount to an AccAddress.
// It will panic if the module account does not exist.
func (k BaseKeeper) SendCoinsFromModuleToAccount(
ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins,
) error {
senderAddr := k.ak.GetModuleAddress(senderModule)
if senderAddr == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
}
return k.SendCoins(ctx, senderAddr, recipientAddr, amt)
}
// SendCoinsFromModuleToModule transfers coins from a ModuleAccount to another.
// It will panic if either module account does not exist.
func (k BaseKeeper) SendCoinsFromModuleToModule(
ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins,
) error {
senderAddr := k.ak.GetModuleAddress(senderModule)
if senderAddr == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
}
recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
if recipientAcc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
}
return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
}
// SendCoinsFromAccountToModule transfers coins from an AccAddress to a ModuleAccount.
// It will panic if the module account does not exist.
func (k BaseKeeper) SendCoinsFromAccountToModule(
ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins,
) error {
recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
if recipientAcc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
}
return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
}
// DelegateCoinsFromAccountToModule delegates coins and transfers them from a
// delegator account to a module account. It will panic if the module account
// does not exist or is unauthorized.
func (k BaseKeeper) DelegateCoinsFromAccountToModule(
ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins,
) error {
recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
if recipientAcc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
}
if !recipientAcc.HasPermission(authtypes.Staking) {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to receive delegated coins", recipientModule))
}
return k.DelegateCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
}
// UndelegateCoinsFromModuleToAccount undelegates the unbonding coins and transfers
// them from a module account to the delegator account. It will panic if the
// module account does not exist or is unauthorized.
func (k BaseKeeper) UndelegateCoinsFromModuleToAccount(
ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins,
) error {
acc := k.ak.GetModuleAccount(ctx, senderModule)
if acc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
}
if !acc.HasPermission(authtypes.Staking) {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to undelegate coins", senderModule))
}
return k.UndelegateCoins(ctx, acc.GetAddress(), recipientAddr, amt)
}
// MintCoins creates new coins from thin air and adds it to the module account.
// It will panic if the module account does not exist or is unauthorized.
func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
acc := k.ak.GetModuleAccount(ctx, moduleName)
if acc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
}
if !acc.HasPermission(authtypes.Minter) {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName))
}
_, err := k.AddCoins(ctx, acc.GetAddress(), amt)
if err != nil {
return err
}
// update total supply
supply := k.GetSupply(ctx)
supply.Inflate(amt)
k.SetSupply(ctx, supply)
logger := k.Logger(ctx)
logger.Info(fmt.Sprintf("minted %s from %s module account", amt.String(), moduleName))
return nil
}
// BurnCoins burns coins deletes coins from the balance of the module account.
// It will panic if the module account does not exist or is unauthorized.
func (k BaseKeeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
acc := k.ak.GetModuleAccount(ctx, moduleName)
if acc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
}
if !acc.HasPermission(authtypes.Burner) {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to burn tokens", moduleName))
}
_, err := k.SubtractCoins(ctx, acc.GetAddress(), amt)
if err != nil {
return err
}
// update total supply
supply := k.GetSupply(ctx)
supply.Deflate(amt)
k.SetSupply(ctx, supply)
logger := k.Logger(ctx)
logger.Info(fmt.Sprintf("burned %s from %s module account", amt.String(), moduleName))
return nil
}
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
vacc.TrackDelegation(blockTime, balance, amt)
}
return nil
}
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)
}
return nil
}
// MarshalSupply marshals a Supply interface. If the given type implements
// the Marshaler interface, it is treated as a Proto-defined message and
// serialized that way. Otherwise, it falls back on the internal Amino codec.
func (k BaseKeeper) MarshalSupply(supplyI exported.SupplyI) ([]byte, error) {
return codec.MarshalAny(k.cdc, supplyI)
}
// UnmarshalSupply returns a Supply interface from raw encoded supply
// bytes of a Proto-based Supply type. An error is returned upon decoding
// failure.
func (k BaseKeeper) UnmarshalSupply(bz []byte) (exported.SupplyI, error) {
var evi exported.SupplyI
if err := codec.UnmarshalAny(k.cdc, &evi, bz); err != nil {
return nil, err
}
return evi, nil
}
// MarshalSupplyJSON JSON encodes a supply object implementing the Supply
// interface.
func (k BaseKeeper) MarshalSupplyJSON(supply exported.SupplyI) ([]byte, error) {
msg, ok := supply.(proto.Message)
if !ok {
return nil, fmt.Errorf("cannot proto marshal %T", supply)
}
any, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, err
}
return k.cdc.MarshalJSON(any)
}
// UnmarshalSupplyJSON returns a Supply from JSON encoded bytes
func (k BaseKeeper) UnmarshalSupplyJSON(bz []byte) (exported.SupplyI, error) {
var any codectypes.Any
if err := k.cdc.UnmarshalJSON(bz, &any); err != nil {
return nil, err
}
var supply exported.SupplyI
if err := k.cdc.UnpackAny(&any, &supply); err != nil {
return nil, err
}
return supply, nil
}