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 }