2020-08-17 17:09:02 +00:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-01-08 00:39:27 +00:00
|
|
|
"strings"
|
2020-08-17 17:09:02 +00:00
|
|
|
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2022-01-08 00:39:27 +00:00
|
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
2020-08-17 17:09:02 +00:00
|
|
|
"github.com/kava-labs/kava/x/issuance/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
// IssueTokens mints new tokens and sends them to the receiver address
|
|
|
|
func (k Keeper) IssueTokens(ctx sdk.Context, tokens sdk.Coin, owner, receiver sdk.AccAddress) error {
|
|
|
|
asset, found := k.GetAsset(ctx, tokens.Denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", tokens.Denom)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(owner.String(), asset.Owner) != 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
|
|
|
}
|
|
|
|
if asset.Paused {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetPaused, "denom: %s", tokens.Denom)
|
|
|
|
}
|
2020-08-21 22:56:20 +00:00
|
|
|
if asset.Blockable {
|
2022-01-08 00:39:27 +00:00
|
|
|
blocked, _ := k.checkBlockedAddress(asset, receiver.String())
|
2020-08-21 22:56:20 +00:00
|
|
|
if blocked {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAccountBlocked, "address: %s", receiver)
|
|
|
|
}
|
2020-08-17 17:09:02 +00:00
|
|
|
}
|
|
|
|
acc := k.accountKeeper.GetAccount(ctx, receiver)
|
2022-01-08 00:39:27 +00:00
|
|
|
_, ok := acc.(authtypes.ModuleAccountI)
|
2020-08-17 17:09:02 +00:00
|
|
|
if ok {
|
|
|
|
return sdkerrors.Wrapf(types.ErrIssueToModuleAccount, "address: %s", receiver)
|
|
|
|
}
|
|
|
|
|
2020-08-21 22:56:20 +00:00
|
|
|
// for rate-limited assets, check that the issuance isn't over the limit
|
|
|
|
if asset.RateLimit.Active {
|
|
|
|
err := k.IncrementCurrentAssetSupply(ctx, tokens)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-17 17:09:02 +00:00
|
|
|
// mint new tokens
|
2022-01-08 00:39:27 +00:00
|
|
|
err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, sdk.NewCoins(tokens))
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// send to receiver
|
2022-01-08 00:39:27 +00:00
|
|
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, receiver, sdk.NewCoins(tokens))
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypeIssue,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyIssueAmount, tokens.String()),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RedeemTokens sends tokens from the owner address to the module account and burns them
|
|
|
|
func (k Keeper) RedeemTokens(ctx sdk.Context, tokens sdk.Coin, owner sdk.AccAddress) error {
|
|
|
|
asset, found := k.GetAsset(ctx, tokens.Denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", tokens.Denom)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(owner.String(), asset.Owner) != 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
|
|
|
}
|
|
|
|
if asset.Paused {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetPaused, "denom: %s", tokens.Denom)
|
|
|
|
}
|
|
|
|
coins := sdk.NewCoins(tokens)
|
2022-01-08 00:39:27 +00:00
|
|
|
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleAccountName, coins)
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
err = k.bankKeeper.BurnCoins(ctx, types.ModuleAccountName, coins)
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypeRedeem,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyRedeemAmount, tokens.String()),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// BlockAddress adds an address to the blocked list
|
|
|
|
func (k Keeper) BlockAddress(ctx sdk.Context, denom string, owner, blockedAddress sdk.AccAddress) error {
|
|
|
|
asset, found := k.GetAsset(ctx, denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
|
|
|
}
|
2020-08-21 22:56:20 +00:00
|
|
|
if !asset.Blockable {
|
|
|
|
return sdkerrors.Wrap(types.ErrAssetUnblockable, denom)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(owner.String(), asset.Owner) != 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
blocked, _ := k.checkBlockedAddress(asset, blockedAddress.String())
|
2020-08-17 17:09:02 +00:00
|
|
|
if blocked {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAccountAlreadyBlocked, "address: %s", blockedAddress)
|
|
|
|
}
|
2020-08-21 22:56:20 +00:00
|
|
|
account := k.accountKeeper.GetAccount(ctx, blockedAddress)
|
|
|
|
if account == nil {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address: %s", blockedAddress)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
asset.BlockedAddresses = append(asset.BlockedAddresses, blockedAddress.String())
|
2020-08-17 17:09:02 +00:00
|
|
|
k.SetAsset(ctx, asset)
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypeBlock,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyBlock, blockedAddress.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyDenom, asset.Denom),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnblockAddress removes an address from the blocked list
|
|
|
|
func (k Keeper) UnblockAddress(ctx sdk.Context, denom string, owner, addr sdk.AccAddress) error {
|
|
|
|
asset, found := k.GetAsset(ctx, denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
|
|
|
}
|
2020-08-21 22:56:20 +00:00
|
|
|
if !asset.Blockable {
|
|
|
|
return sdkerrors.Wrap(types.ErrAssetUnblockable, denom)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(owner.String(), asset.Owner) != 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
blocked, i := k.checkBlockedAddress(asset, addr.String())
|
2020-08-17 17:09:02 +00:00
|
|
|
if !blocked {
|
2021-02-04 18:35:24 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrAccountAlreadyUnblocked, "address: %s", addr)
|
2020-08-17 17:09:02 +00:00
|
|
|
}
|
|
|
|
|
2020-08-21 22:56:20 +00:00
|
|
|
blockedAddrs := k.removeBlockedAddress(asset.BlockedAddresses, i)
|
2020-08-17 17:09:02 +00:00
|
|
|
asset.BlockedAddresses = blockedAddrs
|
|
|
|
k.SetAsset(ctx, asset)
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypeUnblock,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyUnblock, addr.String()),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyDenom, asset.Denom),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPauseStatus pauses/un-pauses an asset
|
|
|
|
func (k Keeper) SetPauseStatus(ctx sdk.Context, owner sdk.AccAddress, denom string, status bool) error {
|
|
|
|
asset, found := k.GetAsset(ctx, denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(owner.String(), asset.Owner) != 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return sdkerrors.Wrapf(types.ErrNotAuthorized, "owner: %s, address: %s", asset.Owner, owner)
|
|
|
|
}
|
|
|
|
if asset.Paused == status {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
asset.Paused = !asset.Paused
|
|
|
|
k.SetAsset(ctx, asset)
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypePause,
|
|
|
|
sdk.NewAttribute(types.AttributeKeyPauseStatus, fmt.Sprintf("%t", status)),
|
|
|
|
sdk.NewAttribute(types.AttributeKeyDenom, asset.Denom),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-21 22:56:20 +00:00
|
|
|
// SeizeCoinsForBlockableAssets seizes coins from blocked addresses for assets that have blocking enabled
|
|
|
|
func (k Keeper) SeizeCoinsForBlockableAssets(ctx sdk.Context) error {
|
|
|
|
params := k.GetParams(ctx)
|
|
|
|
for _, asset := range params.Assets {
|
|
|
|
if asset.Blockable {
|
|
|
|
err := k.SeizeCoinsFromBlockedAddresses(ctx, asset.Denom)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-17 17:09:02 +00:00
|
|
|
// SeizeCoinsFromBlockedAddresses checks blocked addresses for coins of the input denom and transfers them to the owner account
|
|
|
|
func (k Keeper) SeizeCoinsFromBlockedAddresses(ctx sdk.Context, denom string) error {
|
|
|
|
asset, found := k.GetAsset(ctx, denom)
|
|
|
|
if !found {
|
|
|
|
return sdkerrors.Wrapf(types.ErrAssetNotFound, "denom: %s", denom)
|
|
|
|
}
|
|
|
|
for _, address := range asset.BlockedAddresses {
|
2022-01-08 00:39:27 +00:00
|
|
|
addrBech32, err := sdk.AccAddressFromBech32(address)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
account := k.accountKeeper.GetAccount(ctx, addrBech32)
|
2020-08-21 22:56:20 +00:00
|
|
|
if account == nil {
|
|
|
|
// avoids a potential panic
|
|
|
|
// this could happen if, for example, an account was pruned from state but remained in the block list,
|
|
|
|
continue
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
|
|
|
|
coinsAmount := k.bankKeeper.GetAllBalances(ctx, addrBech32).AmountOf(denom)
|
2020-08-17 17:09:02 +00:00
|
|
|
if !coinsAmount.IsPositive() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
coins := sdk.NewCoins(sdk.NewCoin(denom, coinsAmount))
|
2022-01-08 00:39:27 +00:00
|
|
|
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, addrBech32, types.ModuleAccountName, coins)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ownerBech32, err := sdk.AccAddressFromBech32(asset.Owner)
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-08 00:39:27 +00:00
|
|
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, ownerBech32, coins)
|
2020-08-17 17:09:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
sdk.NewEvent(
|
|
|
|
types.EventTypeSeize,
|
|
|
|
sdk.NewAttribute(sdk.AttributeKeyAmount, coins.String()),
|
2022-01-08 00:39:27 +00:00
|
|
|
sdk.NewAttribute(types.AttributeKeyAddress, address),
|
2020-08-17 17:09:02 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-08 00:39:27 +00:00
|
|
|
func (k Keeper) checkBlockedAddress(asset types.Asset, checkAddress string) (bool, int) {
|
2020-08-17 17:09:02 +00:00
|
|
|
for i, address := range asset.BlockedAddresses {
|
2022-01-08 00:39:27 +00:00
|
|
|
if strings.Compare(address, checkAddress) == 0 {
|
2020-08-17 17:09:02 +00:00
|
|
|
return true, i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, 0
|
|
|
|
}
|
|
|
|
|
2022-01-08 00:39:27 +00:00
|
|
|
func (k Keeper) removeBlockedAddress(blockedAddrs []string, i int) []string {
|
2020-08-17 17:09:02 +00:00
|
|
|
blockedAddrs[len(blockedAddrs)-1], blockedAddrs[i] = blockedAddrs[i], blockedAddrs[len(blockedAddrs)-1]
|
|
|
|
return blockedAddrs[:len(blockedAddrs)-1]
|
|
|
|
}
|