github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/bank/internal/keeper/keeper.go (about)

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec"
     9  
    10  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    11  
    12  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    13  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/innertx"
    15  	authexported "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/exported"
    16  	vestexported "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/vesting/exported"
    17  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank/internal/types"
    18  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/params"
    19  	"github.com/fibonacci-chain/fbc/libs/tendermint/global"
    20  )
    21  
    22  var _ Keeper = (*BaseKeeper)(nil)
    23  
    24  // Keeper defines a module interface that facilitates the transfer of coins
    25  // between accounts.
    26  type Keeper interface {
    27  	SendKeeper
    28  
    29  	DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
    30  	UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
    31  
    32  	GetInnerTxKeeper() innertx.InnerTxKeeper
    33  }
    34  
    35  // BaseKeeper manages transfers between accounts. It implements the Keeper interface.
    36  type BaseKeeper struct {
    37  	BaseSendKeeper
    38  
    39  	ak         types.AccountKeeper
    40  	paramSpace params.Subspace
    41  
    42  	marshal *codec.CodecProxy
    43  }
    44  
    45  // NewBaseKeeper returns a new BaseKeeper
    46  func NewBaseKeeper(
    47  	ak types.AccountKeeper, paramSpace params.Subspace, blacklistedAddrs map[string]bool,
    48  ) BaseKeeper {
    49  
    50  	ps := paramSpace.WithKeyTable(types.ParamKeyTable())
    51  	return BaseKeeper{
    52  		BaseSendKeeper: NewBaseSendKeeper(ak, ps, blacklistedAddrs),
    53  		ak:             ak,
    54  		paramSpace:     ps,
    55  	}
    56  }
    57  
    58  func NewBaseKeeperWithMarshal(ak types.AccountKeeper, marshal *codec.CodecProxy, paramSpace params.Subspace, blacklistedAddrs map[string]bool,
    59  ) BaseKeeper {
    60  	ret := NewBaseKeeper(ak, paramSpace, blacklistedAddrs)
    61  	ret.marshal = marshal
    62  	return ret
    63  }
    64  
    65  // DelegateCoins performs delegation by deducting amt coins from an account with
    66  // address addr. For vesting accounts, delegations amounts are tracked for both
    67  // vesting and vested coins.
    68  // The coins are then transferred from the delegator address to a ModuleAccount address.
    69  // If any of the delegation amounts are negative, an error is returned.
    70  func (keeper BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) (err error) {
    71  	defer func() {
    72  		if !ctx.IsCheckTx() && keeper.ik != nil {
    73  			keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, delegatorAddr, moduleAccAddr, innertx.CosmosCallType, innertx.DelegateCallName, amt, err)
    74  		}
    75  	}()
    76  	delegatorAcc := keeper.ak.GetAccount(ctx, delegatorAddr)
    77  	if delegatorAcc == nil {
    78  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", delegatorAddr)
    79  	}
    80  
    81  	moduleAcc := keeper.ak.GetAccount(ctx, moduleAccAddr)
    82  	if moduleAcc == nil {
    83  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
    84  	}
    85  
    86  	if !amt.IsValid() {
    87  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
    88  	}
    89  
    90  	oldCoins := delegatorAcc.GetCoins()
    91  
    92  	_, hasNeg := oldCoins.SafeSub(amt)
    93  	if hasNeg {
    94  		return sdkerrors.Wrapf(
    95  			sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt,
    96  		)
    97  	}
    98  
    99  	if err := trackDelegation(delegatorAcc, ctx.BlockHeader().Time, amt); err != nil {
   100  		return sdkerrors.Wrap(err, "failed to track delegation")
   101  	}
   102  
   103  	keeper.ak.SetAccount(ctx, delegatorAcc)
   104  
   105  	_, err = keeper.AddCoins(ctx, moduleAccAddr, amt)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // UndelegateCoins performs undelegation by crediting amt coins to an account with
   114  // address addr. For vesting accounts, undelegation amounts are tracked for both
   115  // vesting and vested coins.
   116  // The coins are then transferred from a ModuleAccount address to the delegator address.
   117  // If any of the undelegation amounts are negative, an error is returned.
   118  func (keeper BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) (err error) {
   119  	defer func() {
   120  		if !ctx.IsCheckTx() && keeper.ik != nil {
   121  			keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, moduleAccAddr, delegatorAddr, innertx.CosmosCallType, innertx.UndelegateCallName, amt, err)
   122  		}
   123  	}()
   124  
   125  	delegatorAcc := keeper.ak.GetAccount(ctx, delegatorAddr)
   126  	if delegatorAcc == nil {
   127  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", delegatorAddr)
   128  	}
   129  
   130  	moduleAcc := keeper.ak.GetAccount(ctx, moduleAccAddr)
   131  	if moduleAcc == nil {
   132  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
   133  	}
   134  
   135  	if !amt.IsValid() {
   136  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   137  	}
   138  
   139  	oldCoins := moduleAcc.GetCoins()
   140  
   141  	newCoins, hasNeg := oldCoins.SafeSub(amt)
   142  	if hasNeg {
   143  		return sdkerrors.Wrapf(
   144  			sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt,
   145  		)
   146  	}
   147  
   148  	if err = keeper.SetCoins(ctx, moduleAccAddr, newCoins); err != nil {
   149  		return err
   150  	}
   151  
   152  	if err := trackUndelegation(delegatorAcc, amt); err != nil {
   153  		return sdkerrors.Wrap(err, "failed to track undelegation")
   154  	}
   155  
   156  	keeper.ak.SetAccount(ctx, delegatorAcc)
   157  	return nil
   158  }
   159  
   160  // SendKeeper defines a module interface that facilitates the transfer of coins
   161  // between accounts without the possibility of creating coins.
   162  type SendKeeper interface {
   163  	ViewKeeper
   164  
   165  	InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error
   166  	SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
   167  
   168  	SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error)
   169  	AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error)
   170  	SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error
   171  
   172  	GetSendEnabled(ctx sdk.Context) bool
   173  	SetSendEnabled(ctx sdk.Context, enabled bool)
   174  
   175  	BlacklistedAddr(addr sdk.AccAddress) bool
   176  }
   177  
   178  var _ SendKeeper = (*BaseSendKeeper)(nil)
   179  
   180  // BaseSendKeeper only allows transfers between accounts without the possibility of
   181  // creating coins. It implements the SendKeeper interface.
   182  type BaseSendKeeper struct {
   183  	BaseViewKeeper
   184  
   185  	ak         types.AccountKeeper
   186  	ask        authexported.SizerAccountKeeper
   187  	paramSpace params.Subspace
   188  
   189  	// list of addresses that are restricted from receiving transactions
   190  	blacklistedAddrs map[string]bool
   191  
   192  	ik innertx.InnerTxKeeper
   193  }
   194  
   195  // NewBaseSendKeeper returns a new BaseSendKeeper.
   196  func NewBaseSendKeeper(
   197  	ak types.AccountKeeper, paramSpace params.Subspace, blacklistedAddrs map[string]bool,
   198  ) BaseSendKeeper {
   199  
   200  	bsk := BaseSendKeeper{
   201  		BaseViewKeeper:   NewBaseViewKeeper(ak),
   202  		ak:               ak,
   203  		paramSpace:       paramSpace,
   204  		blacklistedAddrs: blacklistedAddrs,
   205  	}
   206  	bsk.ask, _ = bsk.ak.(authexported.SizerAccountKeeper)
   207  	return bsk
   208  }
   209  
   210  // InputOutputCoins handles a list of inputs and outputs
   211  func (keeper BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) (err error) {
   212  	defer func() {
   213  		if !ctx.IsCheckTx() && keeper.ik != nil {
   214  			for _, in := range inputs {
   215  				keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, in.Address, sdk.AccAddress{}, innertx.CosmosCallType, innertx.MultiCallName, in.Coins, err)
   216  			}
   217  
   218  			for _, out := range outputs {
   219  				keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, sdk.AccAddress{}, out.Address, innertx.CosmosCallType, innertx.MultiCallName, out.Coins, err)
   220  			}
   221  		}
   222  	}()
   223  	// Safety check ensuring that when sending coins the keeper must maintain the
   224  	// Check supply invariant and validity of Coins.
   225  	if err := types.ValidateInputsOutputs(inputs, outputs); err != nil {
   226  		return err
   227  	}
   228  
   229  	for _, in := range inputs {
   230  		_, err := keeper.SubtractCoins(ctx, in.Address, in.Coins)
   231  		if err != nil {
   232  			return err
   233  		}
   234  
   235  		ctx.EventManager().EmitEvent(
   236  			sdk.NewEvent(
   237  				sdk.EventTypeMessage,
   238  				sdk.NewAttribute(types.AttributeKeySender, in.Address.String()),
   239  			),
   240  		)
   241  	}
   242  
   243  	for _, out := range outputs {
   244  		_, err := keeper.AddCoins(ctx, out.Address, out.Coins)
   245  		if err != nil {
   246  			return err
   247  		}
   248  
   249  		ctx.EventManager().EmitEvent(
   250  			sdk.NewEvent(
   251  				types.EventTypeTransfer,
   252  				sdk.NewAttribute(types.AttributeKeyRecipient, out.Address.String()),
   253  				sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()),
   254  			),
   255  		)
   256  
   257  		// Create account if recipient does not exist.
   258  		//
   259  		// NOTE: This should ultimately be removed in favor a more flexible approach
   260  		// such as delegated fee messages.
   261  		acc := keeper.ak.GetAccount(ctx, out.Address)
   262  		if acc == nil {
   263  			keeper.ak.SetAccount(ctx, keeper.ak.NewAccountWithAddress(ctx, out.Address))
   264  		}
   265  	}
   266  
   267  	return nil
   268  }
   269  
   270  // SendCoins moves coins from one account to another
   271  func (keeper BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (err error) {
   272  	defer func() {
   273  		if !ctx.IsCheckTx() && keeper.ik != nil {
   274  			keeper.ik.UpdateInnerTx(ctx.TxBytes(), ctx.BlockHeight(), innertx.CosmosDepth, fromAddr, toAddr, innertx.CosmosCallType, innertx.SendCallName, amt, err)
   275  		}
   276  	}()
   277  	fromAddrStr := fromAddr.String()
   278  	ctx.EventManager().EmitEvents(sdk.Events{
   279  		// This event should have all info (to, from, amount) without looking at other events
   280  		sdk.NewEvent(
   281  			types.EventTypeTransfer,
   282  			sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()),
   283  			sdk.NewAttribute(types.AttributeKeySender, fromAddrStr),
   284  			sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()),
   285  		),
   286  		sdk.NewEvent(
   287  			sdk.EventTypeMessage,
   288  			sdk.NewAttribute(types.AttributeKeySender, fromAddrStr),
   289  		),
   290  	})
   291  
   292  	if !amt.IsValid() {
   293  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   294  	}
   295  
   296  	fromAcc, _ := ctx.GetFromAccountCacheData().(authexported.Account)
   297  	toAcc, _ := ctx.GetToAccountCacheData().(authexported.Account)
   298  	fromAccGas, toAccGas := ctx.GetFromAccountCacheGas(), ctx.GetToAccountCacheGas()
   299  
   300  	fromAcc, fromAccGas = keeper.getAccount(&ctx, fromAddr, fromAcc, fromAccGas)
   301  	_, err = keeper.subtractCoins(ctx, fromAddr, fromAcc, fromAccGas, amt)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	ctx.UpdateFromAccountCache(fromAcc, 0)
   307  
   308  	toAcc, toAccGas = keeper.getAccount(&ctx, toAddr, toAcc, toAccGas)
   309  	_, err = keeper.addCoins(ctx, toAddr, toAcc, toAccGas, amt)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	ctx.UpdateToAccountCache(toAcc, 0)
   315  
   316  	return nil
   317  }
   318  
   319  // SubtractCoins subtracts amt from the coins at the addr.
   320  //
   321  // CONTRACT: If the account is a vesting account, the amount has to be spendable.
   322  func (keeper BaseSendKeeper) SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) {
   323  	if !amt.IsValid() {
   324  		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   325  	}
   326  	acc, gasUsed := authexported.GetAccountAndGas(&ctx, keeper.ak, addr)
   327  	return keeper.subtractCoins(ctx, addr, acc, gasUsed, amt)
   328  }
   329  
   330  func (keeper *BaseSendKeeper) subtractCoins(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) (sdk.Coins, error) {
   331  	oldCoins, spendableCoins := sdk.NewCoins(), sdk.NewCoins()
   332  	if acc != nil {
   333  		oldCoins = acc.GetCoins()
   334  		spendableCoins = acc.SpendableCoins(ctx.BlockTime())
   335  	}
   336  
   337  	// For non-vesting accounts, spendable coins will simply be the original coins.
   338  	// So the check here is sufficient instead of subtracting from oldCoins.
   339  	_, hasNeg := spendableCoins.SafeSub(amt)
   340  	if hasNeg {
   341  		return amt, sdkerrors.Wrapf(
   342  			sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", spendableCoins, amt,
   343  		)
   344  	}
   345  
   346  	newCoins := oldCoins.Sub(amt) // should not panic as spendable coins was already checked
   347  	err := keeper.setCoinsToAccount(ctx, addr, acc, accGas, newCoins)
   348  
   349  	return newCoins, err
   350  }
   351  
   352  // AddCoins adds amt to the coins at the addr.
   353  func (keeper BaseSendKeeper) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, error) {
   354  	if !amt.IsValid() {
   355  		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   356  	}
   357  
   358  	// oldCoins := keeper.GetCoins(ctx, addr)
   359  
   360  	acc, gasUsed := authexported.GetAccountAndGas(&ctx, keeper.ak, addr)
   361  	return keeper.addCoins(ctx, addr, acc, gasUsed, amt)
   362  }
   363  
   364  func (keeper *BaseSendKeeper) addCoins(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) (sdk.Coins, error) {
   365  	var oldCoins sdk.Coins
   366  	if acc == nil {
   367  		oldCoins = sdk.NewCoins()
   368  	} else {
   369  		oldCoins = acc.GetCoins()
   370  	}
   371  
   372  	newCoins := oldCoins.Add(amt...)
   373  
   374  	if newCoins.IsAnyNegative() {
   375  		return amt, sdkerrors.Wrapf(
   376  			sdkerrors.ErrInsufficientFunds, "insufficient account funds; %s < %s", oldCoins, amt,
   377  		)
   378  	}
   379  
   380  	err := keeper.setCoinsToAccount(ctx, addr, acc, accGas, newCoins)
   381  
   382  	return newCoins, err
   383  }
   384  
   385  // SetCoins sets the coins at the addr.
   386  func (keeper BaseSendKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
   387  	if !amt.IsValid() {
   388  		sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   389  	}
   390  
   391  	acc := keeper.ak.GetAccount(ctx, addr)
   392  	if acc == nil {
   393  		acc = keeper.ak.NewAccountWithAddress(ctx, addr)
   394  	}
   395  
   396  	err := acc.SetCoins(amt)
   397  	if err != nil {
   398  		panic(err)
   399  	}
   400  
   401  	keeper.ak.SetAccount(ctx, acc)
   402  	return nil
   403  }
   404  
   405  func (keeper *BaseSendKeeper) getAccount(ctx *sdk.Context, addr sdk.AccAddress, acc authexported.Account, getgas sdk.Gas) (authexported.Account, sdk.Gas) {
   406  	gasMeter := ctx.GasMeter()
   407  	if acc != nil && bytes.Equal(acc.GetAddress(), addr) {
   408  		if getgas > 0 {
   409  			gasMeter.ConsumeGas(getgas, "get account")
   410  			return acc, getgas
   411  		}
   412  		if ok, gasused := authexported.TryAddGetAccountGas(gasMeter, keeper.ask, acc); ok {
   413  			return acc, gasused
   414  		}
   415  	}
   416  	return authexported.GetAccountAndGas(ctx, keeper.ak, addr)
   417  }
   418  
   419  func (keeper *BaseSendKeeper) setCoinsToAccount(ctx sdk.Context, addr sdk.AccAddress, acc authexported.Account, accGas sdk.Gas, amt sdk.Coins) error {
   420  	if !amt.IsValid() {
   421  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   422  	}
   423  
   424  	acc, _ = keeper.getAccount(&ctx, addr, acc, accGas)
   425  	if acc == nil {
   426  		acc = keeper.ak.NewAccountWithAddress(ctx, addr)
   427  	}
   428  
   429  	err := acc.SetCoins(amt)
   430  	if err != nil {
   431  		panic(err)
   432  	}
   433  
   434  	keeper.ak.SetAccount(ctx, acc)
   435  	return nil
   436  }
   437  
   438  // GetSendEnabled returns the current SendEnabled
   439  func (keeper BaseSendKeeper) GetSendEnabled(ctx sdk.Context) bool {
   440  	var enabled bool
   441  	keeper.paramSpace.Get(ctx, types.ParamStoreKeySendEnabled, &enabled)
   442  	return enabled
   443  }
   444  
   445  // SetSendEnabled sets the send enabled
   446  func (keeper BaseSendKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) {
   447  	global.Manager.SetSendEnabled(enabled)
   448  	keeper.paramSpace.Set(ctx, types.ParamStoreKeySendEnabled, &enabled)
   449  }
   450  
   451  // BlacklistedAddr checks if a given address is blacklisted (i.e restricted from
   452  // receiving funds)
   453  func (keeper BaseSendKeeper) BlacklistedAddr(addr sdk.AccAddress) bool {
   454  	return keeper.blacklistedAddrs[addr.String()]
   455  }
   456  
   457  // SetInnerTxKeeper set innerTxKeeper
   458  func (k *BaseKeeper) SetInnerTxKeeper(keeper innertx.InnerTxKeeper) {
   459  	k.BaseSendKeeper.SetInnerTxKeeper(keeper)
   460  }
   461  
   462  func (k *BaseSendKeeper) SetInnerTxKeeper(keeper innertx.InnerTxKeeper) {
   463  	k.ik = keeper
   464  }
   465  
   466  func (k BaseSendKeeper) GetInnerTxKeeper() innertx.InnerTxKeeper {
   467  	return k.ik
   468  }
   469  
   470  var _ ViewKeeper = (*BaseViewKeeper)(nil)
   471  
   472  // ViewKeeper defines a module interface that facilitates read only access to
   473  // account balances.
   474  type ViewKeeper interface {
   475  	GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
   476  	HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool
   477  }
   478  
   479  // BaseViewKeeper implements a read only keeper implementation of ViewKeeper.
   480  type BaseViewKeeper struct {
   481  	ak types.AccountKeeper
   482  }
   483  
   484  // NewBaseViewKeeper returns a new BaseViewKeeper.
   485  func NewBaseViewKeeper(ak types.AccountKeeper) BaseViewKeeper {
   486  	return BaseViewKeeper{ak: ak}
   487  }
   488  
   489  // Logger returns a module-specific logger.
   490  func (keeper BaseViewKeeper) Logger(ctx sdk.Context) log.Logger {
   491  	return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
   492  }
   493  
   494  // GetCoins returns the coins at the addr.
   495  func (keeper BaseViewKeeper) GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
   496  	acc := keeper.ak.GetAccount(ctx, addr)
   497  	if acc == nil {
   498  		return sdk.NewCoins()
   499  	}
   500  	return acc.GetCoins()
   501  }
   502  
   503  // HasCoins returns whether or not an account has at least amt coins.
   504  func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool {
   505  	return keeper.GetCoins(ctx, addr).IsAllGTE(amt)
   506  }
   507  
   508  // CONTRACT: assumes that amt is valid.
   509  func trackDelegation(acc authexported.Account, blockTime time.Time, amt sdk.Coins) error {
   510  	vacc, ok := acc.(vestexported.VestingAccount)
   511  	if ok {
   512  		// TODO: return error on account.TrackDelegation
   513  		vacc.TrackDelegation(blockTime, amt)
   514  	}
   515  
   516  	return acc.SetCoins(acc.GetCoins().Sub(amt))
   517  }
   518  
   519  // CONTRACT: assumes that amt is valid.
   520  func trackUndelegation(acc authexported.Account, amt sdk.Coins) error {
   521  	vacc, ok := acc.(vestexported.VestingAccount)
   522  	if ok {
   523  		// TODO: return error on account.TrackUndelegation
   524  		vacc.TrackUndelegation(amt)
   525  	}
   526  
   527  	return acc.SetCoins(acc.GetCoins().Add(amt...))
   528  }