495 lines
16 KiB
Go
495 lines
16 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/types/query"
|
|
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/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
|
|
|
|
InitGenesis(sdk.Context, *types.GenesisState)
|
|
ExportGenesis(sdk.Context) *types.GenesisState
|
|
|
|
GetSupply(ctx sdk.Context, denom string) sdk.Coin
|
|
GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error)
|
|
IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool)
|
|
GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool)
|
|
SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata)
|
|
IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool)
|
|
|
|
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
|
|
|
|
types.QueryServer
|
|
}
|
|
|
|
// BaseKeeper manages transfers between accounts. It implements the Keeper interface.
|
|
type BaseKeeper struct {
|
|
BaseSendKeeper
|
|
|
|
ak types.AccountKeeper
|
|
cdc codec.BinaryMarshaler
|
|
storeKey sdk.StoreKey
|
|
paramSpace paramtypes.Subspace
|
|
}
|
|
|
|
func (k BaseKeeper) GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
supplyStore := prefix.NewStore(store, types.SupplyKey)
|
|
|
|
supply := sdk.NewCoins()
|
|
|
|
pageRes, err := query.Paginate(supplyStore, pagination, func(key, value []byte) error {
|
|
var amount sdk.Int
|
|
err := amount.Unmarshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to convert amount string to Int %v", err)
|
|
}
|
|
supply = append(supply, sdk.NewCoin(string(key), amount))
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return supply, pageRes, nil
|
|
}
|
|
|
|
// NewBaseKeeper returns a new BaseKeeper object with a given codec, dedicated
|
|
// store key, an AccountKeeper implementation, and a parameter Subspace used to
|
|
// store and fetch module parameters. The BaseKeeper also accepts a
|
|
// blocklist map. This blocklist describes the set of addresses that are not allowed
|
|
// to receive funds through direct and explicit actions, for example, by using a MsgSend or
|
|
// by using a SendCoinsFromModuleToAccount execution.
|
|
func NewBaseKeeper(
|
|
cdc codec.BinaryMarshaler,
|
|
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, balances, amt); err != nil {
|
|
return sdkerrors.Wrap(err, "failed to track delegation")
|
|
}
|
|
// emit coin spent event
|
|
ctx.EventManager().EmitEvent(
|
|
types.NewCoinSpentEvent(delegatorAddr, amt),
|
|
)
|
|
|
|
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.subUnlockedCoins(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, denom string) sdk.Coin {
|
|
store := ctx.KVStore(k.storeKey)
|
|
supplyStore := prefix.NewStore(store, types.SupplyKey)
|
|
|
|
bz := supplyStore.Get([]byte(denom))
|
|
if bz == nil {
|
|
return sdk.Coin{
|
|
Denom: denom,
|
|
Amount: sdk.NewInt(0),
|
|
}
|
|
}
|
|
|
|
var amount sdk.Int
|
|
err := amount.Unmarshal(bz)
|
|
if err != nil {
|
|
panic(fmt.Errorf("unable to unmarshal supply value %v", err))
|
|
}
|
|
|
|
return sdk.Coin{
|
|
Denom: denom,
|
|
Amount: amount,
|
|
}
|
|
}
|
|
|
|
// GetDenomMetaData retrieves the denomination metadata
|
|
func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
store = prefix.NewStore(store, types.DenomMetadataKey(denom))
|
|
|
|
bz := store.Get([]byte(denom))
|
|
if bz == nil {
|
|
return types.Metadata{}, false
|
|
}
|
|
|
|
var metadata types.Metadata
|
|
k.cdc.MustUnmarshalBinaryBare(bz, &metadata)
|
|
|
|
return metadata, true
|
|
}
|
|
|
|
// GetAllDenomMetaData retrieves all denominations metadata
|
|
func (k BaseKeeper) GetAllDenomMetaData(ctx sdk.Context) []types.Metadata {
|
|
denomMetaData := make([]types.Metadata, 0)
|
|
k.IterateAllDenomMetaData(ctx, func(metadata types.Metadata) bool {
|
|
denomMetaData = append(denomMetaData, metadata)
|
|
return false
|
|
})
|
|
|
|
return denomMetaData
|
|
}
|
|
|
|
// IterateAllDenomMetaData iterates over all the denominations metadata and
|
|
// provides the metadata to a callback. If true is returned from the
|
|
// callback, iteration is halted.
|
|
func (k BaseKeeper) IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataPrefix)
|
|
|
|
iterator := denomMetaDataStore.Iterator(nil, nil)
|
|
defer iterator.Close()
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var metadata types.Metadata
|
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &metadata)
|
|
|
|
if cb(metadata) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetDenomMetaData sets the denominations metadata
|
|
func (k BaseKeeper) SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
denomMetaDataStore := prefix.NewStore(store, types.DenomMetadataKey(denomMetaData.Base))
|
|
|
|
m := k.cdc.MustMarshalBinaryBare(&denomMetaData)
|
|
denomMetaDataStore.Set([]byte(denomMetaData.Base), m)
|
|
}
|
|
|
|
// SendCoinsFromModuleToAccount transfers coins from a ModuleAccount to an AccAddress.
|
|
// It will panic if the module account does not exist. An error is returned if
|
|
// the recipient address is black-listed or if sending the tokens fails.
|
|
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))
|
|
}
|
|
|
|
if k.BlockedAddr(recipientAddr) {
|
|
return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr)
|
|
}
|
|
|
|
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, amounts 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(), amounts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, amount := range amounts {
|
|
supply := k.GetSupply(ctx, amount.GetDenom())
|
|
supply = supply.Add(amount)
|
|
k.setSupply(ctx, supply)
|
|
}
|
|
|
|
logger := k.Logger(ctx)
|
|
logger.Info("minted coins from module account", "amount", amounts.String(), "from", moduleName)
|
|
|
|
// emit mint event
|
|
ctx.EventManager().EmitEvent(
|
|
types.NewCoinMintEvent(acc.GetAddress(), amounts),
|
|
)
|
|
|
|
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, amounts 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.subUnlockedCoins(ctx, acc.GetAddress(), amounts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, amount := range amounts {
|
|
supply := k.GetSupply(ctx, amount.GetDenom())
|
|
supply = supply.Sub(amount)
|
|
k.setSupply(ctx, supply)
|
|
}
|
|
|
|
logger := k.Logger(ctx)
|
|
logger.Info("burned tokens from module account", "amount", amounts.String(), "from", moduleName)
|
|
|
|
// emit burn event
|
|
ctx.EventManager().EmitEvent(
|
|
types.NewCoinBurnEvent(acc.GetAddress(), amounts),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k BaseKeeper) setSupply(ctx sdk.Context, coin sdk.Coin) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
supplyStore := prefix.NewStore(store, types.SupplyKey)
|
|
|
|
intBytes, err := coin.Amount.Marshal()
|
|
if err != nil {
|
|
panic(fmt.Errorf("unable to marshal amount value %v", err))
|
|
}
|
|
supplyStore.Set([]byte(coin.GetDenom()), intBytes)
|
|
}
|
|
|
|
func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, 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(ctx.BlockHeader().Time, balance, amt)
|
|
k.ak.SetAccount(ctx, acc)
|
|
}
|
|
|
|
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)
|
|
k.ak.SetAccount(ctx, acc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k BaseViewKeeper) IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
supplyStore := prefix.NewStore(store, types.SupplyKey)
|
|
|
|
iterator := supplyStore.Iterator(nil, nil)
|
|
defer iterator.Close()
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var amount sdk.Int
|
|
err := amount.Unmarshal(iterator.Value())
|
|
if err != nil {
|
|
panic(fmt.Errorf("unable to unmarshal supply value %v", err))
|
|
}
|
|
|
|
balance := sdk.Coin{
|
|
Denom: string(iterator.Key()),
|
|
Amount: amount,
|
|
}
|
|
|
|
if cb(balance) {
|
|
break
|
|
}
|
|
}
|
|
}
|