github.com/KiraCore/sekai@v0.3.43/x/basket/keeper/mint_burn_swap.go (about)

     1  package keeper
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/KiraCore/sekai/x/basket/types"
     7  	sdk "github.com/cosmos/cosmos-sdk/types"
     8  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
     9  )
    10  
    11  func (k Keeper) MintBasketToken(ctx sdk.Context, msg *types.MsgBasketTokenMint) error {
    12  	// check if basket is available
    13  	basket, err := k.GetBasketById(ctx, msg.BasketId)
    14  	if err != nil {
    15  		return err
    16  	}
    17  
    18  	if basket.MintsDisabled {
    19  		return types.ErrMintsDisabledBasket
    20  	}
    21  
    22  	sender, err := sdk.AccAddressFromBech32(msg.Sender)
    23  	if err != nil {
    24  		return err
    25  	}
    26  
    27  	err = k.bk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, msg.Deposit)
    28  	if err != nil {
    29  		return err
    30  	}
    31  
    32  	rates, _ := basket.RatesAndIndexes()
    33  
    34  	basketTokenAmount := sdk.ZeroDec()
    35  	for _, token := range msg.Deposit {
    36  		rate, ok := rates[token.Denom]
    37  		if !ok {
    38  			return types.ErrInvalidBasketDepositDenom
    39  		}
    40  
    41  		_, indexes := basket.RatesAndIndexes()
    42  		tokenIndex := indexes[token.Denom]
    43  		if !basket.Tokens[tokenIndex].Deposits {
    44  			return sdkerrors.Wrap(types.ErrDepositsDisabledForToken, fmt.Sprintf("denom=%s", token.Denom))
    45  		}
    46  		basketTokenAmount = basketTokenAmount.Add(sdk.NewDecFromInt(token.Amount).Mul(rate))
    47  	}
    48  
    49  	basketCoin := sdk.NewCoin(basket.GetBasketDenom(), basketTokenAmount.TruncateInt())
    50  
    51  	if basketCoin.Amount.LT(basket.MintsMin) {
    52  		return types.ErrAmountBelowBaksetMintsMin
    53  	}
    54  
    55  	// register action and check mints max
    56  	k.RegisterMintAction(ctx, msg.BasketId, basketCoin.Amount)
    57  	if k.GetLimitsPeriodMintAmount(ctx, msg.BasketId, basket.LimitsPeriod).GT(basket.MintsMax) {
    58  		return types.ErrAmountAboveBaksetMintsMax
    59  	}
    60  
    61  	basketCoins := sdk.Coins{basketCoin}
    62  	err = k.bk.MintCoins(ctx, types.ModuleName, basketCoins)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	err = k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, basketCoins)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	basket, err = basket.IncreaseBasketTokens(msg.Deposit)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	err = basket.ValidateTokensCap()
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	basket.Amount = basket.Amount.Add(basketCoin.Amount)
    82  	k.SetBasket(ctx, basket)
    83  	return nil
    84  }
    85  
    86  func (k Keeper) BurnBasketToken(ctx sdk.Context, msg *types.MsgBasketTokenBurn) error {
    87  	// check if basket is available
    88  	basket, err := k.GetBasketById(ctx, msg.BasketId)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	if basket.BurnsDisabled {
    94  		return types.ErrBurnsDisabledBasket
    95  	}
    96  
    97  	if msg.BurnAmount.Amount.LT(basket.BurnsMin) {
    98  		return types.ErrAmountBelowBaksetBurnsMin
    99  	}
   100  
   101  	// register action and check burns max
   102  	k.RegisterBurnAction(ctx, msg.BasketId, msg.BurnAmount.Amount)
   103  	if k.GetLimitsPeriodBurnAmount(ctx, msg.BasketId, basket.LimitsPeriod).GT(basket.BurnsMax) {
   104  		return types.ErrAmountAboveBaksetBurnsMax
   105  	}
   106  
   107  	sender, err := sdk.AccAddressFromBech32(msg.Sender)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	burnCoins := sdk.Coins{msg.BurnAmount}
   113  	err = k.bk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, burnCoins)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	err = k.bk.BurnCoins(ctx, types.ModuleName, burnCoins)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	if msg.BurnAmount.Denom != basket.GetBasketDenom() {
   124  		return types.ErrInvalidBasketDenom
   125  	}
   126  
   127  	supply := k.bk.GetSupply(ctx, msg.BurnAmount.Denom)
   128  	portion := sdk.NewDecFromInt(msg.BurnAmount.Amount).Quo(sdk.NewDecFromInt(supply.Amount))
   129  
   130  	withdrawCoins := sdk.Coins{}
   131  	for _, token := range basket.Tokens {
   132  		if !token.Withdraws {
   133  			continue
   134  		}
   135  		withdrawAmount := sdk.NewDecFromInt(token.Amount).Mul(portion).TruncateInt()
   136  		if withdrawAmount.IsPositive() {
   137  			withdrawCoins = withdrawCoins.Add(sdk.NewCoin(token.Denom, withdrawAmount))
   138  		}
   139  	}
   140  
   141  	if withdrawCoins.IsZero() {
   142  		return types.ErrNotAbleToWithdrawAnyTokens
   143  	}
   144  
   145  	err = k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, withdrawCoins)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	basket, err = basket.DecreaseBasketTokens(withdrawCoins)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	err = basket.ValidateTokensCap()
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	basket.Amount = basket.Amount.Sub(msg.BurnAmount.Amount)
   161  	k.SetBasket(ctx, basket)
   162  	return nil
   163  }
   164  
   165  func (k Keeper) BasketSwap(ctx sdk.Context, msg *types.MsgBasketTokenSwap) error {
   166  	// check if basket is available
   167  	basket, err := k.GetBasketById(ctx, msg.BasketId)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	if basket.SwapsDisabled {
   173  		return types.ErrSwapsDisabledBasket
   174  	}
   175  
   176  	oldDisbalance := basket.AverageDisbalance()
   177  
   178  	sender, err := sdk.AccAddressFromBech32(msg.Sender)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	outAmounts := sdk.Coins{}
   184  	for _, pair := range msg.Pairs {
   185  		inCoins := sdk.Coins{pair.InAmount}
   186  		err = k.bk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, inCoins)
   187  		if err != nil {
   188  			return err
   189  		}
   190  
   191  		rates, indexes := basket.RatesAndIndexes()
   192  
   193  		inRate, ok := rates[pair.InAmount.Denom]
   194  		if !ok {
   195  			return types.ErrInvalidBasketDepositDenom
   196  		}
   197  
   198  		outRate, ok := rates[pair.OutToken]
   199  		if !ok {
   200  			return types.ErrInvalidBasketWithdrawDenom
   201  		}
   202  
   203  		inTokenIndex := indexes[pair.InAmount.Denom]
   204  		if !basket.Tokens[inTokenIndex].Swaps {
   205  			return types.ErrSwapsDisabledForInToken
   206  		}
   207  
   208  		outTokenIndex := indexes[pair.OutToken]
   209  		if !basket.Tokens[outTokenIndex].Swaps {
   210  			return types.ErrSwapsDisabledForOutToken
   211  		}
   212  
   213  		swapValue := sdk.NewDecFromInt(pair.InAmount.Amount).Mul(inRate).TruncateInt()
   214  		if swapValue.LT(basket.SwapsMin) {
   215  			return types.ErrAmountBelowBaksetSwapsMin
   216  		}
   217  
   218  		// register action and check swaps max
   219  		k.RegisterSwapAction(ctx, msg.BasketId, swapValue)
   220  		if k.GetLimitsPeriodSwapAmount(ctx, msg.BasketId, basket.LimitsPeriod).GT(basket.SwapsMax) {
   221  			return types.ErrAmountAboveBaksetSwapsMax
   222  		}
   223  
   224  		// calculate out amount considering fees and rates
   225  		swapAmount := sdk.NewDecFromInt(pair.InAmount.Amount).Mul(sdk.OneDec().Sub(basket.SwapFee)).TruncateInt()
   226  
   227  		// pay network for fee
   228  		feeAmount := pair.InAmount.Amount.Sub(swapAmount)
   229  		if feeAmount.IsPositive() {
   230  			basket.Surplus = sdk.Coins(basket.Surplus).Add(sdk.NewCoin(pair.InAmount.Denom, feeAmount))
   231  		}
   232  
   233  		outAmount := sdk.NewDecFromInt(swapAmount).Mul(inRate).Quo(outRate).TruncateInt()
   234  		if outAmount.IsZero() {
   235  			return types.ErrNotAbleToWithdrawAnyTokens
   236  		}
   237  
   238  		// increase in tokens
   239  		basket, err = basket.IncreaseBasketTokens(sdk.Coins{sdk.NewCoin(pair.InAmount.Denom, swapAmount)})
   240  		if err != nil {
   241  			return err
   242  		}
   243  
   244  		outCoins := sdk.Coins{sdk.NewCoin(pair.OutToken, outAmount)}
   245  		// decrease out tokens
   246  		basket, err = basket.DecreaseBasketTokens(outCoins)
   247  		if err != nil {
   248  			return err
   249  		}
   250  
   251  		outAmounts = outAmounts.Add(outCoins...)
   252  	}
   253  
   254  	// calculate slippage fee
   255  	slippageFee := basket.SlippageFee(oldDisbalance)
   256  	finalOutCoins := sdk.Coins{}
   257  	for _, coin := range outAmounts {
   258  		finalOutAmount := sdk.NewDecFromInt(coin.Amount).Mul(sdk.OneDec().Sub(slippageFee)).TruncateInt()
   259  		finalOutCoins = finalOutCoins.Add(sdk.NewCoin(coin.Denom, finalOutAmount))
   260  	}
   261  	err = k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, finalOutCoins)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	// increase surplus by slippage fee
   267  	slippageFeeAmounts := outAmounts.Sub(finalOutCoins...)
   268  	basket.Surplus = sdk.Coins(basket.Surplus).Add(slippageFeeAmounts...)
   269  
   270  	err = basket.ValidateTokensCap()
   271  	if err != nil {
   272  		return err
   273  	}
   274  	k.SetBasket(ctx, basket)
   275  	return nil
   276  }
   277  
   278  func (k Keeper) BasketWithdrawSurplus(ctx sdk.Context, p types.ProposalBasketWithdrawSurplus) error {
   279  	withdrawTarget, err := sdk.AccAddressFromBech32(p.WithdrawTarget)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	for _, basketId := range p.BasketIds {
   285  		// check if basket is available
   286  		basket, err := k.GetBasketById(ctx, basketId)
   287  		if err != nil {
   288  			return err
   289  		}
   290  
   291  		err = k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawTarget, sdk.Coins(basket.Surplus))
   292  		if err != nil {
   293  			return err
   294  		}
   295  
   296  		basket.Surplus = sdk.Coins{}
   297  		k.SetBasket(ctx, basket)
   298  	}
   299  
   300  	// withdraw delegation rewards
   301  	delegator := k.ak.GetModuleAccount(ctx, types.ModuleName).GetAddress()
   302  	k.mk.RegisterDelegator(ctx, delegator)
   303  	rewards := k.mk.ClaimRewardsFromModule(ctx, types.ModuleName)
   304  	if rewards.IsAllPositive() {
   305  		err = k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawTarget, rewards)
   306  		if err != nil {
   307  			return err
   308  		}
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  func (k Keeper) RegisterBasketModuleAsDelegator(ctx sdk.Context) error {
   315  	// withdraw delegation rewards
   316  	delegator := k.ak.GetModuleAccount(ctx, types.ModuleName).GetAddress()
   317  	k.mk.RegisterDelegator(ctx, delegator)
   318  
   319  	return nil
   320  }