github.com/Finschia/finschia-sdk@v0.48.1/x/bank/keeper/keeper.go (about)

     1  package keeper
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/Finschia/finschia-sdk/codec"
     7  	"github.com/Finschia/finschia-sdk/internal/conv"
     8  	"github.com/Finschia/finschia-sdk/store/prefix"
     9  	sdk "github.com/Finschia/finschia-sdk/types"
    10  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    11  	"github.com/Finschia/finschia-sdk/types/query"
    12  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    13  	vestexported "github.com/Finschia/finschia-sdk/x/auth/vesting/exported"
    14  	"github.com/Finschia/finschia-sdk/x/bank/types"
    15  	paramtypes "github.com/Finschia/finschia-sdk/x/params/types"
    16  )
    17  
    18  var _ Keeper = (*BaseKeeper)(nil)
    19  
    20  // Keeper defines a module interface that facilitates the transfer of coins
    21  // between accounts.
    22  type Keeper interface {
    23  	SendKeeper
    24  
    25  	InitGenesis(sdk.Context, *types.GenesisState)
    26  	ExportGenesis(sdk.Context) *types.GenesisState
    27  
    28  	GetSupply(ctx sdk.Context, denom string) sdk.Coin
    29  	HasSupply(ctx sdk.Context, denom string) bool
    30  	GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error)
    31  	IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool)
    32  
    33  	GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool)
    34  	SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata)
    35  	IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool)
    36  
    37  	SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
    38  	SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
    39  	SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
    40  	DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
    41  	UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
    42  	MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
    43  	BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
    44  
    45  	DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
    46  	UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
    47  
    48  	types.QueryServer
    49  }
    50  
    51  // BaseKeeper manages transfers between accounts. It implements the Keeper interface.
    52  type BaseKeeper struct {
    53  	BaseSendKeeper
    54  
    55  	ak                     types.AccountKeeper
    56  	cdc                    codec.BinaryCodec
    57  	storeKey               sdk.StoreKey
    58  	paramSpace             paramtypes.Subspace
    59  	mintCoinsRestrictionFn MintingRestrictionFn
    60  }
    61  
    62  type MintingRestrictionFn func(ctx sdk.Context, coins sdk.Coins) error
    63  
    64  // GetPaginatedTotalSupply queries for the supply, ignoring 0 coins, with a given pagination
    65  func (k BaseKeeper) GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) {
    66  	store := ctx.KVStore(k.storeKey)
    67  	supplyStore := prefix.NewStore(store, types.SupplyKey)
    68  
    69  	supply := sdk.NewCoins()
    70  
    71  	pageRes, err := query.Paginate(supplyStore, pagination, func(key, value []byte) error {
    72  		var amount sdk.Int
    73  		err := amount.Unmarshal(value)
    74  		if err != nil {
    75  			return fmt.Errorf("unable to convert amount string to Int %v", err)
    76  		}
    77  
    78  		// `Add` omits the 0 coins addition to the `supply`.
    79  		supply = supply.Add(sdk.NewCoin(string(key), amount))
    80  		return nil
    81  	})
    82  	if err != nil {
    83  		return nil, nil, err
    84  	}
    85  
    86  	return supply, pageRes, nil
    87  }
    88  
    89  // NewBaseKeeper returns a new BaseKeeper object with a given codec, dedicated
    90  // store key, an AccountKeeper implementation, and a parameter Subspace used to
    91  // store and fetch module parameters. The BaseKeeper also accepts a
    92  // blocklist map. This blocklist describes the set of addresses that are not allowed
    93  // to receive funds through direct and explicit actions, for example, by using a MsgSend or
    94  // by using a SendCoinsFromModuleToAccount execution.
    95  func NewBaseKeeper(
    96  	cdc codec.BinaryCodec,
    97  	storeKey sdk.StoreKey,
    98  	ak types.AccountKeeper,
    99  	paramSpace paramtypes.Subspace,
   100  	blockedAddrs map[string]bool,
   101  ) BaseKeeper {
   102  	// set KeyTable if it has not already been set
   103  	if !paramSpace.HasKeyTable() {
   104  		paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
   105  	}
   106  
   107  	return BaseKeeper{
   108  		BaseSendKeeper:         NewBaseSendKeeper(cdc, storeKey, ak, paramSpace, blockedAddrs),
   109  		ak:                     ak,
   110  		cdc:                    cdc,
   111  		storeKey:               storeKey,
   112  		paramSpace:             paramSpace,
   113  		mintCoinsRestrictionFn: func(ctx sdk.Context, coins sdk.Coins) error { return nil },
   114  	}
   115  }
   116  
   117  // WithMintCoinsRestriction restricts the bank Keeper used within a specific module to
   118  // have restricted permissions on minting via function passed in parameter.
   119  // Previous restriction functions can be nested as such:
   120  //
   121  //	bankKeeper.WithMintCoinsRestriction(restriction1).WithMintCoinsRestriction(restriction2)
   122  func (k BaseKeeper) WithMintCoinsRestriction(check MintingRestrictionFn) BaseKeeper {
   123  	oldRestrictionFn := k.mintCoinsRestrictionFn
   124  	k.mintCoinsRestrictionFn = func(ctx sdk.Context, coins sdk.Coins) error {
   125  		err := check(ctx, coins)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		err = oldRestrictionFn(ctx, coins)
   130  		if err != nil {
   131  			return err
   132  		}
   133  		return nil
   134  	}
   135  	return k
   136  }
   137  
   138  // DelegateCoins performs delegation by deducting amt coins from an account with
   139  // address addr. For vesting accounts, delegations amounts are tracked for both
   140  // vesting and vested coins. The coins are then transferred from the delegator
   141  // address to a ModuleAccount address. If any of the delegation amounts are negative,
   142  // an error is returned.
   143  func (k BaseKeeper) DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error {
   144  	moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr)
   145  	if moduleAcc == nil {
   146  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
   147  	}
   148  
   149  	if !amt.IsValid() {
   150  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   151  	}
   152  
   153  	balances := sdk.NewCoins()
   154  
   155  	for _, coin := range amt {
   156  		balance := k.GetBalance(ctx, delegatorAddr, coin.GetDenom())
   157  		if balance.IsLT(coin) {
   158  			return sdkerrors.Wrapf(
   159  				sdkerrors.ErrInsufficientFunds, "failed to delegate; %s is smaller than %s", balance, amt,
   160  			)
   161  		}
   162  
   163  		balances = balances.Add(balance)
   164  		err := k.setBalance(ctx, delegatorAddr, balance.Sub(coin))
   165  		if err != nil {
   166  			return err
   167  		}
   168  	}
   169  
   170  	if err := k.trackDelegation(ctx, delegatorAddr, balances, amt); err != nil {
   171  		return sdkerrors.Wrap(err, "failed to track delegation")
   172  	}
   173  	// emit coin spent event
   174  	ctx.EventManager().EmitEvent(
   175  		types.NewCoinSpentEvent(delegatorAddr, amt),
   176  	)
   177  
   178  	err := k.addCoins(ctx, moduleAccAddr, amt)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // UndelegateCoins performs undelegation by crediting amt coins to an account with
   187  // address addr. For vesting accounts, undelegation amounts are tracked for both
   188  // vesting and vested coins. The coins are then transferred from a ModuleAccount
   189  // address to the delegator address. If any of the undelegation amounts are
   190  // negative, an error is returned.
   191  func (k BaseKeeper) UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error {
   192  	moduleAcc := k.ak.GetAccount(ctx, moduleAccAddr)
   193  	if moduleAcc == nil {
   194  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleAccAddr)
   195  	}
   196  
   197  	if !amt.IsValid() {
   198  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
   199  	}
   200  
   201  	err := k.subUnlockedCoins(ctx, moduleAccAddr, amt)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	if err := k.trackUndelegation(ctx, delegatorAddr, amt); err != nil {
   207  		return sdkerrors.Wrap(err, "failed to track undelegation")
   208  	}
   209  
   210  	err = k.addCoins(ctx, delegatorAddr, amt)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // GetSupply retrieves the Supply from store
   219  func (k BaseKeeper) GetSupply(ctx sdk.Context, denom string) sdk.Coin {
   220  	store := ctx.KVStore(k.storeKey)
   221  	supplyStore := prefix.NewStore(store, types.SupplyKey)
   222  
   223  	bz := supplyStore.Get([]byte(denom))
   224  	if bz == nil {
   225  		return sdk.Coin{
   226  			Denom:  denom,
   227  			Amount: sdk.NewInt(0),
   228  		}
   229  	}
   230  
   231  	var amount sdk.Int
   232  	err := amount.Unmarshal(bz)
   233  	if err != nil {
   234  		panic(fmt.Errorf("unable to unmarshal supply value %v", err))
   235  	}
   236  
   237  	return sdk.Coin{
   238  		Denom:  denom,
   239  		Amount: amount,
   240  	}
   241  }
   242  
   243  // HasSupply checks if the supply coin exists in store.
   244  func (k BaseKeeper) HasSupply(ctx sdk.Context, denom string) bool {
   245  	store := ctx.KVStore(k.storeKey)
   246  	supplyStore := prefix.NewStore(store, types.SupplyKey)
   247  	return supplyStore.Has([]byte(denom))
   248  }
   249  
   250  // GetDenomMetaData retrieves the denomination metadata. returns the metadata and true if the denom exists,
   251  // false otherwise.
   252  func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) {
   253  	store := ctx.KVStore(k.storeKey)
   254  	store = prefix.NewStore(store, types.DenomMetadataPrefix)
   255  
   256  	bz := store.Get([]byte(denom))
   257  	if bz == nil {
   258  		return types.Metadata{}, false
   259  	}
   260  
   261  	var metadata types.Metadata
   262  	k.cdc.MustUnmarshal(bz, &metadata)
   263  
   264  	return metadata, true
   265  }
   266  
   267  // GetAllDenomMetaData retrieves all denominations metadata
   268  func (k BaseKeeper) GetAllDenomMetaData(ctx sdk.Context) []types.Metadata {
   269  	denomMetaData := make([]types.Metadata, 0)
   270  	k.IterateAllDenomMetaData(ctx, func(metadata types.Metadata) bool {
   271  		denomMetaData = append(denomMetaData, metadata)
   272  		return false
   273  	})
   274  
   275  	return denomMetaData
   276  }
   277  
   278  // IterateAllDenomMetaData iterates over all the denominations metadata and
   279  // provides the metadata to a callback. If true is returned from the
   280  // callback, iteration is halted.
   281  func (k BaseKeeper) IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) {
   282  	store := ctx.KVStore(k.storeKey)
   283  	denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataPrefix)
   284  
   285  	iterator := denomMetaDataStore.Iterator(nil, nil)
   286  	defer iterator.Close()
   287  
   288  	for ; iterator.Valid(); iterator.Next() {
   289  		var metadata types.Metadata
   290  		k.cdc.MustUnmarshal(iterator.Value(), &metadata)
   291  
   292  		if cb(metadata) {
   293  			break
   294  		}
   295  	}
   296  }
   297  
   298  // SetDenomMetaData sets the denominations metadata
   299  func (k BaseKeeper) SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) {
   300  	store := ctx.KVStore(k.storeKey)
   301  	denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataPrefix)
   302  
   303  	m := k.cdc.MustMarshal(&denomMetaData)
   304  	denomMetaDataStore.Set([]byte(denomMetaData.Base), m)
   305  }
   306  
   307  // SendCoinsFromModuleToAccount transfers coins from a ModuleAccount to an AccAddress.
   308  // It will panic if the module account does not exist. An error is returned if
   309  // the recipient address is black-listed or if sending the tokens fails.
   310  func (k BaseKeeper) SendCoinsFromModuleToAccount(
   311  	ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins,
   312  ) error {
   313  	senderAddr := k.ak.GetModuleAddress(senderModule)
   314  	if senderAddr.Empty() {
   315  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
   316  	}
   317  
   318  	if k.BlockedAddr(recipientAddr) {
   319  		return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr)
   320  	}
   321  
   322  	return k.SendCoins(ctx, senderAddr, recipientAddr, amt)
   323  }
   324  
   325  // SendCoinsFromModuleToModule transfers coins from a ModuleAccount to another.
   326  // It will panic if either module account does not exist.
   327  func (k BaseKeeper) SendCoinsFromModuleToModule(
   328  	ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins,
   329  ) error {
   330  	senderAddr := k.ak.GetModuleAddress(senderModule)
   331  	if senderAddr.Empty() {
   332  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
   333  	}
   334  
   335  	recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
   336  	if recipientAcc == nil {
   337  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
   338  	}
   339  
   340  	return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
   341  }
   342  
   343  // SendCoinsFromAccountToModule transfers coins from an AccAddress to a ModuleAccount.
   344  // It will panic if the module account does not exist.
   345  func (k BaseKeeper) SendCoinsFromAccountToModule(
   346  	ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins,
   347  ) error {
   348  	recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
   349  	if recipientAcc == nil {
   350  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
   351  	}
   352  
   353  	return k.SendCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
   354  }
   355  
   356  // DelegateCoinsFromAccountToModule delegates coins and transfers them from a
   357  // delegator account to a module account. It will panic if the module account
   358  // does not exist or is unauthorized.
   359  func (k BaseKeeper) DelegateCoinsFromAccountToModule(
   360  	ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins,
   361  ) error {
   362  	recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule)
   363  	if recipientAcc == nil {
   364  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule))
   365  	}
   366  
   367  	if !recipientAcc.HasPermission(authtypes.Staking) {
   368  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to receive delegated coins", recipientModule))
   369  	}
   370  
   371  	return k.DelegateCoins(ctx, senderAddr, recipientAcc.GetAddress(), amt)
   372  }
   373  
   374  // UndelegateCoinsFromModuleToAccount undelegates the unbonding coins and transfers
   375  // them from a module account to the delegator account. It will panic if the
   376  // module account does not exist or is unauthorized.
   377  func (k BaseKeeper) UndelegateCoinsFromModuleToAccount(
   378  	ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins,
   379  ) error {
   380  	acc := k.ak.GetModuleAccount(ctx, senderModule)
   381  	if acc == nil {
   382  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule))
   383  	}
   384  
   385  	if !acc.HasPermission(authtypes.Staking) {
   386  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to undelegate coins", senderModule))
   387  	}
   388  
   389  	return k.UndelegateCoins(ctx, acc.GetAddress(), recipientAddr, amt)
   390  }
   391  
   392  // MintCoins creates new coins from thin air and adds it to the module account.
   393  // It will panic if the module account does not exist or is unauthorized.
   394  func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error {
   395  	err := k.mintCoinsRestrictionFn(ctx, amounts)
   396  	if err != nil {
   397  		ctx.Logger().Error(fmt.Sprintf("Module %q attempted to mint coins %s it doesn't have permission for, error %v", moduleName, amounts, err))
   398  		return err
   399  	}
   400  	acc := k.ak.GetModuleAccount(ctx, moduleName)
   401  	if acc == nil {
   402  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
   403  	}
   404  
   405  	if !acc.HasPermission(authtypes.Minter) {
   406  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName))
   407  	}
   408  
   409  	err = k.addCoins(ctx, acc.GetAddress(), amounts)
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	for _, amount := range amounts {
   415  		supply := k.GetSupply(ctx, amount.GetDenom())
   416  		supply = supply.Add(amount)
   417  		k.setSupply(ctx, supply)
   418  	}
   419  
   420  	logger := k.Logger(ctx)
   421  	logger.Info("minted coins from module account", "amount", amounts.String(), "from", moduleName)
   422  
   423  	// emit mint event
   424  	ctx.EventManager().EmitEvent(
   425  		types.NewCoinMintEvent(acc.GetAddress(), amounts),
   426  	)
   427  
   428  	return nil
   429  }
   430  
   431  // BurnCoins burns coins deletes coins from the balance of the module account.
   432  // It will panic if the module account does not exist or is unauthorized.
   433  func (k BaseKeeper) BurnCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error {
   434  	acc := k.ak.GetModuleAccount(ctx, moduleName)
   435  	if acc == nil {
   436  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
   437  	}
   438  
   439  	if !acc.HasPermission(authtypes.Burner) {
   440  		panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to burn tokens", moduleName))
   441  	}
   442  
   443  	err := k.subUnlockedCoins(ctx, acc.GetAddress(), amounts)
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	for _, amount := range amounts {
   449  		supply := k.GetSupply(ctx, amount.GetDenom())
   450  		supply = supply.Sub(amount)
   451  		k.setSupply(ctx, supply)
   452  	}
   453  
   454  	logger := k.Logger(ctx)
   455  	logger.Info("burned tokens from module account", "amount", amounts.String(), "from", moduleName)
   456  
   457  	// emit burn event
   458  	ctx.EventManager().EmitEvent(
   459  		types.NewCoinBurnEvent(acc.GetAddress(), amounts),
   460  	)
   461  
   462  	return nil
   463  }
   464  
   465  // setSupply sets the supply for the given coin
   466  func (k BaseKeeper) setSupply(ctx sdk.Context, coin sdk.Coin) {
   467  	intBytes, err := coin.Amount.Marshal()
   468  	if err != nil {
   469  		panic(fmt.Errorf("unable to marshal amount value %v", err))
   470  	}
   471  
   472  	store := ctx.KVStore(k.storeKey)
   473  	supplyStore := prefix.NewStore(store, types.SupplyKey)
   474  
   475  	// Bank invariants and IBC requires to remove zero coins.
   476  	if coin.IsZero() {
   477  		supplyStore.Delete(conv.UnsafeStrToBytes(coin.GetDenom()))
   478  	} else {
   479  		supplyStore.Set([]byte(coin.GetDenom()), intBytes)
   480  	}
   481  }
   482  
   483  // trackDelegation tracks the delegation of the given account if it is a vesting account
   484  func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, balance, amt sdk.Coins) error {
   485  	acc := k.ak.GetAccount(ctx, addr)
   486  	if acc == nil {
   487  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
   488  	}
   489  
   490  	vacc, ok := acc.(vestexported.VestingAccount)
   491  	if ok {
   492  		// TODO: return error on account.TrackDelegation
   493  		vacc.TrackDelegation(ctx.BlockHeader().Time, balance, amt)
   494  		k.ak.SetAccount(ctx, acc)
   495  	}
   496  
   497  	return nil
   498  }
   499  
   500  // trackUndelegation trakcs undelegation of the given account if it is a vesting account
   501  func (k BaseKeeper) trackUndelegation(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
   502  	acc := k.ak.GetAccount(ctx, addr)
   503  	if acc == nil {
   504  		return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
   505  	}
   506  
   507  	vacc, ok := acc.(vestexported.VestingAccount)
   508  	if ok {
   509  		// TODO: return error on account.TrackUndelegation
   510  		vacc.TrackUndelegation(amt)
   511  		k.ak.SetAccount(ctx, acc)
   512  	}
   513  
   514  	return nil
   515  }
   516  
   517  // IterateTotalSupply iterates over the total supply calling the given cb (callback) function
   518  // with the balance of each coin.
   519  // The iteration stops if the callback returns true.
   520  func (k BaseViewKeeper) IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool) {
   521  	store := ctx.KVStore(k.storeKey)
   522  	supplyStore := prefix.NewStore(store, types.SupplyKey)
   523  
   524  	iterator := supplyStore.Iterator(nil, nil)
   525  	defer iterator.Close()
   526  
   527  	for ; iterator.Valid(); iterator.Next() {
   528  		var amount sdk.Int
   529  		err := amount.Unmarshal(iterator.Value())
   530  		if err != nil {
   531  			panic(fmt.Errorf("unable to unmarshal supply value %v", err))
   532  		}
   533  
   534  		balance := sdk.Coin{
   535  			Denom:  string(iterator.Key()),
   536  			Amount: amount,
   537  		}
   538  
   539  		if cb(balance) {
   540  			break
   541  		}
   542  	}
   543  }