github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/ammswap/handler.go (about)

     1  package ammswap
     2  
     3  import (
     4  	"github.com/fibonacci-chain/fbc/x/ammswap/keeper"
     5  	"github.com/fibonacci-chain/fbc/x/ammswap/types"
     6  	"github.com/fibonacci-chain/fbc/x/common"
     7  	"github.com/fibonacci-chain/fbc/x/common/perf"
     8  
     9  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    10  )
    11  
    12  // NewHandler creates an sdk.Handler for all the ammswap type messages
    13  func NewHandler(k Keeper) sdk.Handler {
    14  	return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
    15  		ctx.SetEventManager(sdk.NewEventManager())
    16  		var handlerFun func() (*sdk.Result, error)
    17  		var name string
    18  		switch msg := msg.(type) {
    19  		case types.MsgAddLiquidity:
    20  			name = "handleMsgAddLiquidity"
    21  			handlerFun = func() (*sdk.Result, error) {
    22  				return handleMsgAddLiquidity(ctx, k, msg)
    23  			}
    24  		case types.MsgRemoveLiquidity:
    25  			name = "handleMsgRemoveLiquidity"
    26  			handlerFun = func() (*sdk.Result, error) {
    27  				return handleMsgRemoveLiquidity(ctx, k, msg)
    28  			}
    29  		case types.MsgCreateExchange:
    30  			name = "handleMsgCreateExchange"
    31  			handlerFun = func() (*sdk.Result, error) {
    32  				return handleMsgCreateExchange(ctx, k, msg)
    33  			}
    34  		case types.MsgTokenToToken:
    35  			name = "handleMsgTokenToToken"
    36  			handlerFun = func() (*sdk.Result, error) {
    37  				return handleMsgTokenToToken(ctx, k, msg)
    38  			}
    39  		default:
    40  			return nil, types.ErrSwapUnknownMsgType()
    41  		}
    42  		seq := perf.GetPerf().OnDeliverTxEnter(ctx, types.ModuleName, name)
    43  		defer perf.GetPerf().OnDeliverTxExit(ctx, types.ModuleName, name, seq)
    44  
    45  		res, err := handlerFun()
    46  		common.SanityCheckHandler(res, err)
    47  		return res, err
    48  	}
    49  }
    50  
    51  func handleMsgTokenToToken(ctx sdk.Context, k Keeper, msg types.MsgTokenToToken) (*sdk.Result, error) {
    52  	_, err := k.GetSwapTokenPair(ctx, msg.GetSwapTokenPairName())
    53  	if err != nil {
    54  		return swapTokenByRouter(ctx, k, msg)
    55  	} else {
    56  		return swapToken(ctx, k, msg)
    57  	}
    58  }
    59  
    60  func handleMsgCreateExchange(ctx sdk.Context, k Keeper, msg types.MsgCreateExchange) (*sdk.Result, error) {
    61  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
    62  
    63  	// 0. check if 2 tokens exist
    64  	err := k.IsTokenExist(ctx, msg.Token0Name)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	err = k.IsTokenExist(ctx, msg.Token1Name)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// 1. check if the token pair exists
    75  	tokenPairName := msg.GetSwapTokenPairName()
    76  	_, err = k.GetSwapTokenPair(ctx, tokenPairName)
    77  	if err == nil {
    78  		return types.ErrSwapTokenPairExist().Result()
    79  	}
    80  
    81  	// 2. check if the pool token exists
    82  	poolTokenName := types.GetPoolTokenName(msg.Token0Name, msg.Token1Name)
    83  	_, err = k.GetPoolTokenInfo(ctx, poolTokenName)
    84  	if err == nil {
    85  		return types.ErrPoolTokenPairExist().Result()
    86  	}
    87  
    88  	// 3. create the pool token
    89  	k.NewPoolToken(ctx, poolTokenName)
    90  
    91  	// 4. create the token pair
    92  	swapTokenPair := types.NewSwapPair(msg.Token0Name, msg.Token1Name)
    93  	k.SetSwapTokenPair(ctx, tokenPairName, swapTokenPair)
    94  
    95  	// 5. notify backend module
    96  	k.OnCreateExchange(ctx, swapTokenPair)
    97  
    98  	event = event.AppendAttributes(sdk.NewAttribute("pool-token-name", poolTokenName))
    99  	event = event.AppendAttributes(sdk.NewAttribute("token-pair", tokenPairName))
   100  	ctx.EventManager().EmitEvent(event)
   101  	return &sdk.Result{Events: ctx.EventManager().Events()}, nil
   102  }
   103  
   104  func handleMsgAddLiquidity(ctx sdk.Context, k Keeper, msg types.MsgAddLiquidity) (*sdk.Result, error) {
   105  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   106  	if msg.Deadline < ctx.BlockTime().Unix() {
   107  		return types.ErrBlockTimeBigThanDeadline().Result()
   108  	}
   109  	swapTokenPair, err := k.GetSwapTokenPair(ctx, msg.GetSwapTokenPairName())
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	baseTokens := sdk.NewDecCoinFromDec(msg.MaxBaseAmount.Denom, sdk.ZeroDec())
   114  	var liquidity sdk.Dec
   115  	poolToken, err := k.GetPoolTokenInfo(ctx, swapTokenPair.PoolTokenName)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	if swapTokenPair.QuotePooledCoin.Amount.IsZero() && swapTokenPair.BasePooledCoin.Amount.IsZero() {
   120  		baseTokens.Amount = msg.MaxBaseAmount.Amount
   121  		liquidity = sdk.NewDec(1)
   122  	} else if swapTokenPair.BasePooledCoin.IsPositive() && swapTokenPair.QuotePooledCoin.IsPositive() {
   123  		baseTokens.Amount = common.MulAndQuo(msg.QuoteAmount.Amount, swapTokenPair.BasePooledCoin.Amount, swapTokenPair.QuotePooledCoin.Amount)
   124  		totalSupply := k.GetPoolTokenAmount(ctx, swapTokenPair.PoolTokenName)
   125  		if baseTokens.IsZero() {
   126  			baseTokens.Amount = sdk.NewDecWithPrec(1, sdk.Precision)
   127  		}
   128  		if totalSupply.IsZero() {
   129  			return types.ErrIsZeroValue("totalSupply").Result()
   130  		}
   131  		liquidity = common.MulAndQuo(msg.QuoteAmount.Amount, totalSupply, swapTokenPair.QuotePooledCoin.Amount)
   132  		if liquidity.IsZero() {
   133  			return types.ErrIsZeroValue("liquidity").Result()
   134  		}
   135  	} else {
   136  		return types.ErrInvalidTokenPair(swapTokenPair.String()).Result()
   137  	}
   138  	if baseTokens.Amount.GT(msg.MaxBaseAmount.Amount) {
   139  		return types.ErrBaseTokensAmountBiggerThanMax().Result()
   140  	}
   141  	if liquidity.LT(msg.MinLiquidity) {
   142  		return types.ErrLessThan("liquidity", "min liquidity").Result()
   143  	}
   144  
   145  	// transfer coins
   146  	coins := sdk.SysCoins{
   147  		msg.QuoteAmount,
   148  		baseTokens,
   149  	}
   150  
   151  	coins = coinSort(coins)
   152  
   153  	err = k.SendCoinsToPool(ctx, coins, msg.Sender)
   154  	if err != nil {
   155  		return types.ErrSendCoinsFailed(err).Result()
   156  	}
   157  	// update swapTokenPair
   158  	swapTokenPair.QuotePooledCoin = swapTokenPair.QuotePooledCoin.Add(msg.QuoteAmount)
   159  	swapTokenPair.BasePooledCoin = swapTokenPair.BasePooledCoin.Add(baseTokens)
   160  	k.SetSwapTokenPair(ctx, msg.GetSwapTokenPairName(), swapTokenPair)
   161  
   162  	// update poolToken
   163  	poolCoins := sdk.NewDecCoinFromDec(poolToken.Symbol, liquidity)
   164  	err = k.MintPoolCoinsToUser(ctx, sdk.SysCoins{poolCoins}, msg.Sender)
   165  	if err != nil {
   166  		return types.ErrMintPoolTokenFailed(err).Result()
   167  	}
   168  
   169  	event.AppendAttributes(sdk.NewAttribute("liquidity", liquidity.String()))
   170  	event.AppendAttributes(sdk.NewAttribute("baseAmount", baseTokens.String()))
   171  	ctx.EventManager().EmitEvent(event)
   172  	return &sdk.Result{Events: ctx.EventManager().Events()}, nil
   173  }
   174  
   175  func handleMsgRemoveLiquidity(ctx sdk.Context, k Keeper, msg types.MsgRemoveLiquidity) (*sdk.Result, error) {
   176  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   177  
   178  	if msg.Deadline < ctx.BlockTime().Unix() {
   179  		return types.ErrMsgDeadlineLessThanBlockTime().Result()
   180  	}
   181  	swapTokenPair, err := k.GetSwapTokenPair(ctx, msg.GetSwapTokenPairName())
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	liquidity := msg.Liquidity
   187  	poolTokenAmount := k.GetPoolTokenAmount(ctx, swapTokenPair.PoolTokenName)
   188  	if poolTokenAmount.LT(liquidity) {
   189  		return types.ErrLessThan("pool token amount", "liquidity").Result()
   190  	}
   191  
   192  	baseDec := common.MulAndQuo(swapTokenPair.BasePooledCoin.Amount, liquidity, poolTokenAmount)
   193  	quoteDec := common.MulAndQuo(swapTokenPair.QuotePooledCoin.Amount, liquidity, poolTokenAmount)
   194  
   195  	baseAmount := sdk.NewDecCoinFromDec(swapTokenPair.BasePooledCoin.Denom, baseDec)
   196  	quoteAmount := sdk.NewDecCoinFromDec(swapTokenPair.QuotePooledCoin.Denom, quoteDec)
   197  
   198  	if baseAmount.IsLT(msg.MinBaseAmount) {
   199  		return types.ErrLessThan("base amount", "min base amount").Result()
   200  	}
   201  	if quoteAmount.IsLT(msg.MinQuoteAmount) {
   202  		return types.ErrLessThan("quote amount", "min quote amount").Result()
   203  	}
   204  
   205  	// transfer coins
   206  	coins := sdk.SysCoins{
   207  		baseAmount,
   208  		quoteAmount,
   209  	}
   210  	coins = coinSort(coins)
   211  	err = k.SendCoinsFromPoolToAccount(ctx, coins, msg.Sender)
   212  	if err != nil {
   213  		return types.ErrSendCoinsFromPoolToAccountFailed(err.Error()).Result()
   214  	}
   215  	// update swapTokenPair
   216  	swapTokenPair.QuotePooledCoin = swapTokenPair.QuotePooledCoin.Sub(quoteAmount)
   217  	swapTokenPair.BasePooledCoin = swapTokenPair.BasePooledCoin.Sub(baseAmount)
   218  	k.SetSwapTokenPair(ctx, msg.GetSwapTokenPairName(), swapTokenPair)
   219  
   220  	// update poolToken
   221  	poolCoins := sdk.NewDecCoinFromDec(swapTokenPair.PoolTokenName, liquidity)
   222  	err = k.BurnPoolCoinsFromUser(ctx, sdk.SysCoins{poolCoins}, msg.Sender)
   223  	if err != nil {
   224  		return types.ErrBurnPoolTokenFailed(err).Result()
   225  	}
   226  
   227  	event.AppendAttributes(sdk.NewAttribute("quoteAmount", quoteAmount.String()))
   228  	event.AppendAttributes(sdk.NewAttribute("baseAmount", baseAmount.String()))
   229  	ctx.EventManager().EmitEvent(event)
   230  	return &sdk.Result{Events: ctx.EventManager().Events()}, nil
   231  }
   232  
   233  func swapToken(ctx sdk.Context, k Keeper, msg types.MsgTokenToToken) (*sdk.Result, error) {
   234  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   235  
   236  	if err := common.HasSufficientCoins(msg.Sender, k.GetTokenKeeper().GetCoins(ctx, msg.Sender),
   237  		sdk.SysCoins{msg.SoldTokenAmount}); err != nil {
   238  		return common.ErrInsufficientCoins(DefaultParamspace, err.Error()).Result()
   239  	}
   240  	if msg.Deadline < ctx.BlockTime().Unix() {
   241  		return types.ErrBlockTimeBigThanDeadline().Result()
   242  	}
   243  	swapTokenPair, err := k.GetSwapTokenPair(ctx, msg.GetSwapTokenPairName())
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	if swapTokenPair.BasePooledCoin.IsZero() || swapTokenPair.QuotePooledCoin.IsZero() {
   248  		return types.ErrIsZeroValue("base pooled coin or quote pooled coin").Result()
   249  	}
   250  	params := k.GetParams(ctx)
   251  	tokenBuy := keeper.CalculateTokenToBuy(swapTokenPair, msg.SoldTokenAmount, msg.MinBoughtTokenAmount.Denom, params)
   252  	if tokenBuy.IsZero() {
   253  		return types.ErrIsZeroValue("token buy").Result()
   254  	}
   255  	if tokenBuy.Amount.LT(msg.MinBoughtTokenAmount.Amount) {
   256  		return types.ErrLessThan("token buy amount", "min bought token amount").Result()
   257  	}
   258  
   259  	res, err := swapTokenNativeToken(ctx, k, swapTokenPair, tokenBuy, msg)
   260  	if err != nil {
   261  		return res, err
   262  	}
   263  	event.AppendAttributes(sdk.NewAttribute("bought_token_amount", tokenBuy.String()))
   264  	event.AppendAttributes(sdk.NewAttribute("recipient", msg.Recipient.String()))
   265  	ctx.EventManager().EmitEvent(event)
   266  	return &sdk.Result{Events: ctx.EventManager().Events()}, nil
   267  }
   268  
   269  func swapTokenByRouter(ctx sdk.Context, k Keeper, msg types.MsgTokenToToken) (*sdk.Result, error) {
   270  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   271  
   272  	if msg.Deadline < ctx.BlockTime().Unix() {
   273  		return types.ErrBlockTimeBigThanDeadline().Result()
   274  	}
   275  	if err := common.HasSufficientCoins(msg.Sender, k.GetTokenKeeper().GetCoins(ctx, msg.Sender),
   276  		sdk.SysCoins{msg.SoldTokenAmount}); err != nil {
   277  		return common.ErrInsufficientCoins(DefaultParamspace, err.Error()).Result()
   278  	}
   279  	tokenPairOne := types.GetSwapTokenPairName(msg.SoldTokenAmount.Denom, sdk.DefaultBondDenom)
   280  	swapTokenPairOne, err := k.GetSwapTokenPair(ctx, tokenPairOne)
   281  	if err != nil {
   282  		return nil, types.ErrNonExistSwapTokenPair(msg.GetSwapTokenPairName())
   283  	}
   284  	if swapTokenPairOne.BasePooledCoin.IsZero() || swapTokenPairOne.QuotePooledCoin.IsZero() {
   285  		return types.ErrIsZeroValue("base pooled coin or quote pooled coin").Result()
   286  	}
   287  	tokenPairTwo := types.GetSwapTokenPairName(msg.MinBoughtTokenAmount.Denom, sdk.DefaultBondDenom)
   288  	swapTokenPairTwo, err := k.GetSwapTokenPair(ctx, tokenPairTwo)
   289  	if err != nil {
   290  		return nil, types.ErrNonExistSwapTokenPair(msg.GetSwapTokenPairName())
   291  	}
   292  	if swapTokenPairTwo.BasePooledCoin.IsZero() || swapTokenPairTwo.QuotePooledCoin.IsZero() {
   293  		return types.ErrIsZeroValue("base pooled coin or quote pooled coin").Result()
   294  	}
   295  
   296  	nativeAmount := sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.MustNewDecFromStr("0"))
   297  	params := k.GetParams(ctx)
   298  	msgOne := msg
   299  	msgOne.MinBoughtTokenAmount = nativeAmount
   300  	tokenNative := keeper.CalculateTokenToBuy(swapTokenPairOne, msgOne.SoldTokenAmount, msgOne.MinBoughtTokenAmount.Denom, params)
   301  	if tokenNative.IsZero() {
   302  		return types.ErrIsZeroValue("token native").Result()
   303  	}
   304  	msgTwo := msg
   305  	msgTwo.SoldTokenAmount = tokenNative
   306  	tokenBuy := keeper.CalculateTokenToBuy(swapTokenPairTwo, msgTwo.SoldTokenAmount, msgTwo.MinBoughtTokenAmount.Denom, params)
   307  	// sanity check. user may set MinBoughtTokenAmount to zero on front end.
   308  	// if set zero,this will not return err
   309  	if tokenBuy.IsZero() {
   310  		return types.ErrIsZeroValue("token to buy amount").Result()
   311  	}
   312  	if tokenBuy.Amount.LT(msg.MinBoughtTokenAmount.Amount) {
   313  		return types.ErrLessThan("token buy amount", "min bought token amount").Result()
   314  	}
   315  
   316  	res, err := swapTokenNativeToken(ctx, k, swapTokenPairOne, tokenNative, msgOne)
   317  	if err != nil {
   318  		return res, err
   319  	}
   320  	res, err = swapTokenNativeToken(ctx, k, swapTokenPairTwo, tokenBuy, msgTwo)
   321  	if err != nil {
   322  		return res, err
   323  	}
   324  
   325  	event.AppendAttributes(sdk.NewAttribute("bought_token_amount", tokenBuy.String()))
   326  	event.AppendAttributes(sdk.NewAttribute("recipient", msg.Recipient.String()))
   327  	ctx.EventManager().EmitEvent(event)
   328  	return &sdk.Result{Events: ctx.EventManager().Events()}, nil
   329  }
   330  
   331  func swapTokenNativeToken(
   332  	ctx sdk.Context, k Keeper, swapTokenPair SwapTokenPair, tokenBuy sdk.SysCoin,
   333  	msg types.MsgTokenToToken,
   334  ) (*sdk.Result, error) {
   335  	// transfer coins
   336  	err := k.SendCoinsToPool(ctx, sdk.SysCoins{msg.SoldTokenAmount}, msg.Sender)
   337  	if err != nil {
   338  		return types.ErrSendCoinsToPoolFailed(err.Error()).Result()
   339  	}
   340  
   341  	err = k.SendCoinsFromPoolToAccount(ctx, sdk.SysCoins{tokenBuy}, msg.Recipient)
   342  	if err != nil {
   343  		return types.ErrSendCoinsFromPoolToAccountFailed(err.Error()).Result()
   344  	}
   345  
   346  	// update swapTokenPair
   347  	if msg.MinBoughtTokenAmount.Denom < msg.SoldTokenAmount.Denom {
   348  		swapTokenPair.QuotePooledCoin = swapTokenPair.QuotePooledCoin.Add(msg.SoldTokenAmount)
   349  		swapTokenPair.BasePooledCoin = swapTokenPair.BasePooledCoin.Sub(tokenBuy)
   350  	} else {
   351  		swapTokenPair.QuotePooledCoin = swapTokenPair.QuotePooledCoin.Sub(tokenBuy)
   352  		swapTokenPair.BasePooledCoin = swapTokenPair.BasePooledCoin.Add(msg.SoldTokenAmount)
   353  	}
   354  	k.SetSwapTokenPair(ctx, msg.GetSwapTokenPairName(), swapTokenPair)
   355  	k.OnSwapToken(ctx, msg.Recipient, swapTokenPair, msg.SoldTokenAmount, tokenBuy)
   356  	return &sdk.Result{}, nil
   357  }
   358  
   359  func coinSort(coins sdk.SysCoins) sdk.SysCoins {
   360  	var newCoins sdk.SysCoins
   361  	for _, coin := range coins {
   362  		if coin.Amount.IsPositive() {
   363  			newCoins = append(newCoins, coin)
   364  		}
   365  	}
   366  	newCoins = newCoins.Sort()
   367  	return newCoins
   368  }