2024-05-24 19:03:09 +00:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
2024-06-28 02:40:17 +00:00
|
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
2024-05-24 19:03:09 +00:00
|
|
|
|
|
|
|
"github.com/kava-labs/kava/x/precisebank/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
// MintCoins creates new coins from thin air and adds it to the module account.
|
|
|
|
// If ExtendedCoinDenom is provided, the corresponding fractional amount is
|
|
|
|
// added to the module state.
|
|
|
|
// It will panic if the module account does not exist or is unauthorized.
|
|
|
|
func (k Keeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error {
|
|
|
|
// Disallow minting to x/precisebank module
|
|
|
|
if moduleName == types.ModuleName {
|
|
|
|
panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s cannot be minted to", moduleName))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: MintingRestrictionFn is not used in x/precisebank
|
|
|
|
// Panic errors are identical to x/bank for consistency.
|
|
|
|
acc := k.ak.GetModuleAccount(ctx, moduleName)
|
|
|
|
if acc == nil {
|
|
|
|
panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !acc.HasPermission(authtypes.Minter) {
|
|
|
|
panic(errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the coins are valid before minting
|
|
|
|
if !amt.IsValid() {
|
|
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, amt.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get non-ExtendedCoinDenom coins
|
|
|
|
passthroughCoins := amt
|
|
|
|
|
|
|
|
extendedAmount := amt.AmountOf(types.ExtendedCoinDenom)
|
|
|
|
if extendedAmount.IsPositive() {
|
|
|
|
// Remove ExtendedCoinDenom from the coins as it is managed by x/precisebank
|
|
|
|
removeCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedAmount)
|
|
|
|
passthroughCoins = amt.Sub(removeCoin)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Coins unmanaged by x/precisebank are passed through to x/bank
|
|
|
|
if !passthroughCoins.Empty() {
|
|
|
|
if err := k.bk.MintCoins(ctx, moduleName, passthroughCoins); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-28 02:40:17 +00:00
|
|
|
// Only mint extended coin if the amount is positive
|
|
|
|
if extendedAmount.IsPositive() {
|
|
|
|
if err := k.mintExtendedCoin(ctx, moduleName, extendedAmount); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fullEmissionCoin := types.SumExtendedCoin(amt)
|
|
|
|
if fullEmissionCoin.IsZero() {
|
2024-05-24 19:03:09 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-28 02:40:17 +00:00
|
|
|
ctx.EventManager().EmitEvent(
|
|
|
|
banktypes.NewCoinMintEvent(acc.GetAddress(), sdk.NewCoins(fullEmissionCoin)),
|
|
|
|
)
|
|
|
|
|
|
|
|
return nil
|
2024-05-24 19:03:09 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 22:20:13 +00:00
|
|
|
// mintExtendedCoin manages the minting of only extended coins. This also
|
|
|
|
// handles integer carry over from fractional balance to integer balance if
|
|
|
|
// necessary depending on the fractional balance and minting amount. Ensures
|
|
|
|
// that the reserve fully backs the additional minted amount, minting any extra
|
|
|
|
// reserve integer coins if necessary.
|
|
|
|
// 4 Cases:
|
|
|
|
// 1. NO integer carry over, >= 0 remainder - no reserve mint
|
|
|
|
// 2. NO integer carry over, negative remainder - mint 1 to reserve
|
|
|
|
// 3. Integer carry over, >= 0 remainder
|
|
|
|
// - Transfer 1 integer from reserve -> account
|
|
|
|
//
|
|
|
|
// 4. Integer carry over, negative remainder
|
|
|
|
// - Transfer 1 integer from reserve -> account
|
|
|
|
// - Mint 1 to reserve
|
|
|
|
// Optimization:
|
|
|
|
// - Increase direct account mint amount by 1, no extra reserve mint
|
2024-05-24 19:03:09 +00:00
|
|
|
func (k Keeper) mintExtendedCoin(
|
|
|
|
ctx sdk.Context,
|
2024-06-20 22:20:13 +00:00
|
|
|
recipientModuleName string,
|
2024-05-24 19:03:09 +00:00
|
|
|
amt sdkmath.Int,
|
|
|
|
) error {
|
2024-06-20 22:20:13 +00:00
|
|
|
moduleAddr := k.ak.GetModuleAddress(recipientModuleName)
|
2024-05-24 19:03:09 +00:00
|
|
|
|
|
|
|
// Get current module account fractional balance - 0 if not found
|
|
|
|
fractionalAmount := k.GetFractionalBalance(ctx, moduleAddr)
|
|
|
|
|
|
|
|
// Get separated mint amounts
|
|
|
|
integerMintAmount := amt.Quo(types.ConversionFactor())
|
|
|
|
fractionalMintAmount := amt.Mod(types.ConversionFactor())
|
|
|
|
|
2024-06-20 22:20:13 +00:00
|
|
|
// Get previous remainder amount, as we need to it before carry calculation
|
|
|
|
// for the optimization path.
|
|
|
|
prevRemainder := k.GetRemainderAmount(ctx)
|
|
|
|
|
|
|
|
// Deduct new remainder with minted fractional amount. This will result in
|
|
|
|
// two cases:
|
|
|
|
// 1. Zero or positive remainder: remainder is sufficient to back the minted
|
|
|
|
// fractional amount. Reserve is also sufficient to back the minted amount
|
|
|
|
// so no additional reserve integer coin is needed.
|
|
|
|
// 2. Negative remainder: remainder is insufficient to back the minted
|
|
|
|
// fractional amount. Reserve will need to be increased to back the mint
|
|
|
|
// amount.
|
|
|
|
newRemainder := prevRemainder.Sub(fractionalMintAmount)
|
|
|
|
|
2024-05-24 19:03:09 +00:00
|
|
|
// Get new fractional balance after minting, this could be greater than
|
|
|
|
// the conversion factor and must be checked for carry over to integer mint
|
|
|
|
// amount as being set as-is may cause fractional balance exceeding max.
|
|
|
|
newFractionalBalance := fractionalAmount.Add(fractionalMintAmount)
|
|
|
|
|
2024-06-20 22:20:13 +00:00
|
|
|
// Case #3 - Integer carry, remainder is sufficient (0 or positive)
|
|
|
|
if newFractionalBalance.GTE(types.ConversionFactor()) && newRemainder.GTE(sdkmath.ZeroInt()) {
|
|
|
|
// Carry should send from reserve -> account, instead of minting an
|
|
|
|
// extra integer coin. Otherwise doing an extra mint will require a burn
|
|
|
|
// from reserves to maintain exact backing.
|
|
|
|
carryCoin := sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt())
|
|
|
|
|
|
|
|
// SendCoinsFromModuleToModule allows for sending coins even if the
|
|
|
|
// recipient module account is blocked.
|
|
|
|
if err := k.bk.SendCoinsFromModuleToModule(
|
|
|
|
ctx,
|
|
|
|
types.ModuleName,
|
|
|
|
recipientModuleName,
|
|
|
|
sdk.NewCoins(carryCoin),
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Case #4 - Integer carry, remainder is insufficient
|
|
|
|
// This is the optimization path where the integer mint amount is increased
|
|
|
|
// by 1, instead of doing both a reserve -> account transfer and reserve mint.
|
|
|
|
if newFractionalBalance.GTE(types.ConversionFactor()) && newRemainder.IsNegative() {
|
|
|
|
integerMintAmount = integerMintAmount.AddRaw(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it carries over, adjust the fractional balance to account for the
|
|
|
|
// previously added 1 integer amount.
|
2024-05-24 19:03:09 +00:00
|
|
|
// fractional amounts x and y where both x and y < ConversionFactor
|
|
|
|
// x + y < (2 * ConversionFactor) - 2
|
|
|
|
// x + y < 1 integer amount + fractional amount
|
|
|
|
if newFractionalBalance.GTE(types.ConversionFactor()) {
|
|
|
|
// Subtract 1 integer equivalent amount of fractional balance. Same
|
|
|
|
// behavior as using .Mod() in this case.
|
|
|
|
newFractionalBalance = newFractionalBalance.Sub(types.ConversionFactor())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mint new integer amounts in x/bank - including carry over from fractional
|
|
|
|
// amount if any.
|
|
|
|
if integerMintAmount.IsPositive() {
|
|
|
|
integerMintCoin := sdk.NewCoin(types.IntegerCoinDenom, integerMintAmount)
|
|
|
|
|
|
|
|
if err := k.bk.MintCoins(
|
|
|
|
ctx,
|
2024-06-20 22:20:13 +00:00
|
|
|
recipientModuleName,
|
2024-05-24 19:03:09 +00:00
|
|
|
sdk.NewCoins(integerMintCoin),
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign new fractional balance in x/precisebank
|
|
|
|
k.SetFractionalBalance(ctx, moduleAddr, newFractionalBalance)
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// Update remainder & reserves to back minted fractional coins
|
|
|
|
|
2024-06-20 22:20:13 +00:00
|
|
|
// Mint an additional reserve integer coin if remainder is insufficient.
|
|
|
|
// The remainder is the amount of fractional coins that can be minted and
|
|
|
|
// still be fully backed by reserve. If the remainder is less than the
|
|
|
|
// minted fractional amount, then the reserve needs to be increased to
|
|
|
|
// back the additional fractional amount.
|
|
|
|
// Optimization: This is only done when the integer amount does NOT carry,
|
|
|
|
// as a direct account mint is done instead of integer carry transfer +
|
|
|
|
// insufficient remainder reserve mint.
|
|
|
|
wasCarried := fractionalAmount.Add(fractionalMintAmount).GTE(types.ConversionFactor())
|
|
|
|
if prevRemainder.LT(fractionalMintAmount) && !wasCarried {
|
|
|
|
// Always only 1 integer coin, as fractionalMintAmount < ConversionFactor
|
2024-05-24 19:03:09 +00:00
|
|
|
reserveMintCoins := sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.OneInt()))
|
|
|
|
if err := k.bk.MintCoins(ctx, types.ModuleName, reserveMintCoins); err != nil {
|
|
|
|
return fmt.Errorf("failed to mint %s for reserve: %w", reserveMintCoins, err)
|
|
|
|
}
|
2024-06-20 22:20:13 +00:00
|
|
|
}
|
2024-05-24 19:03:09 +00:00
|
|
|
|
2024-06-20 22:20:13 +00:00
|
|
|
// newRemainder will be negative if prevRemainder < fractionalMintAmount.
|
|
|
|
// This needs to be adjusted back to the corresponding positive value. The
|
|
|
|
// remainder will be always < conversionFactor after add if it is negative.
|
|
|
|
if newRemainder.IsNegative() {
|
2024-05-24 19:03:09 +00:00
|
|
|
newRemainder = newRemainder.Add(types.ConversionFactor())
|
|
|
|
}
|
|
|
|
|
|
|
|
k.SetRemainderAmount(ctx, newRemainder)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|