github.com/gravity-devs/liquidity@v1.5.3/x/liquidity/keeper/batch.go (about)

     1  package keeper
     2  
     3  import (
     4  	sdk "github.com/cosmos/cosmos-sdk/types"
     5  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
     6  
     7  	"github.com/gravity-devs/liquidity/x/liquidity/types"
     8  )
     9  
    10  // DeleteAndInitPoolBatches resets batch msg states that were previously executed
    11  // and deletes msg states that were marked to be deleted.
    12  func (k Keeper) DeleteAndInitPoolBatches(ctx sdk.Context) {
    13  	k.IterateAllPoolBatches(ctx, func(poolBatch types.PoolBatch) bool {
    14  		// Re-initialize the executed batch.
    15  		if poolBatch.Executed {
    16  			// On the other hand, BatchDeposit, BatchWithdraw, is all handled by the endblock if there is no error.
    17  			// If there are BatchMsgs left, reset the Executed, Succeeded flag so that it can be executed in the next batch.
    18  			depositMsgs := k.GetAllRemainingPoolBatchDepositMsgStates(ctx, poolBatch)
    19  			if len(depositMsgs) > 0 {
    20  				for _, msg := range depositMsgs {
    21  					msg.Executed = false
    22  					msg.Succeeded = false
    23  				}
    24  				k.SetPoolBatchDepositMsgStatesByPointer(ctx, poolBatch.PoolId, depositMsgs)
    25  			}
    26  
    27  			withdrawMsgs := k.GetAllRemainingPoolBatchWithdrawMsgStates(ctx, poolBatch)
    28  			if len(withdrawMsgs) > 0 {
    29  				for _, msg := range withdrawMsgs {
    30  					msg.Executed = false
    31  					msg.Succeeded = false
    32  				}
    33  				k.SetPoolBatchWithdrawMsgStatesByPointer(ctx, poolBatch.PoolId, withdrawMsgs)
    34  			}
    35  
    36  			height := ctx.BlockHeight()
    37  
    38  			// In the case of remaining swap msg states, those are either fractionally matched
    39  			// or has not yet been expired.
    40  			swapMsgs := k.GetAllRemainingPoolBatchSwapMsgStates(ctx, poolBatch)
    41  			if len(swapMsgs) > 0 {
    42  				for _, msg := range swapMsgs {
    43  					if height > msg.OrderExpiryHeight {
    44  						msg.ToBeDeleted = true
    45  					} else {
    46  						msg.Executed = false
    47  						msg.Succeeded = false
    48  					}
    49  				}
    50  				k.SetPoolBatchSwapMsgStatesByPointer(ctx, poolBatch.PoolId, swapMsgs)
    51  			}
    52  
    53  			// Delete all batch msg states that are ready to be deleted.
    54  			k.DeleteAllReadyPoolBatchDepositMsgStates(ctx, poolBatch)
    55  			k.DeleteAllReadyPoolBatchWithdrawMsgStates(ctx, poolBatch)
    56  			k.DeleteAllReadyPoolBatchSwapMsgStates(ctx, poolBatch)
    57  
    58  			if err := k.InitNextPoolBatch(ctx, poolBatch); err != nil {
    59  				panic(err)
    60  			}
    61  		}
    62  		return false
    63  	})
    64  }
    65  
    66  // InitNextPoolBatch re-initializes the batch and increases the batch index.
    67  func (k Keeper) InitNextPoolBatch(ctx sdk.Context, poolBatch types.PoolBatch) error {
    68  	if !poolBatch.Executed {
    69  		return types.ErrBatchNotExecuted
    70  	}
    71  
    72  	poolBatch.Index++
    73  	poolBatch.BeginHeight = ctx.BlockHeight()
    74  	poolBatch.Executed = false
    75  
    76  	k.SetPoolBatch(ctx, poolBatch)
    77  	return nil
    78  }
    79  
    80  // ExecutePoolBatches executes the accumulated msgs in the batch.
    81  // The order is (1)swap, (2)deposit, (3)withdraw.
    82  func (k Keeper) ExecutePoolBatches(ctx sdk.Context) {
    83  	params := k.GetParams(ctx)
    84  	logger := k.Logger(ctx)
    85  
    86  	k.IterateAllPoolBatches(ctx, func(poolBatch types.PoolBatch) bool {
    87  		if !poolBatch.Executed && ctx.BlockHeight()%int64(params.UnitBatchHeight) == 0 {
    88  			executedMsgCount, err := k.SwapExecution(ctx, poolBatch)
    89  			if err != nil {
    90  				panic(err)
    91  			}
    92  
    93  			k.IterateAllPoolBatchDepositMsgStates(ctx, poolBatch, func(batchMsg types.DepositMsgState) bool {
    94  				if batchMsg.Executed || batchMsg.ToBeDeleted || batchMsg.Succeeded {
    95  					return false
    96  				}
    97  				executedMsgCount++
    98  				if err := k.ExecuteDeposit(ctx, batchMsg, poolBatch); err != nil {
    99  					logger.Error("deposit failed",
   100  						"poolID", poolBatch.PoolId,
   101  						"batchIndex", poolBatch.Index,
   102  						"msgIndex", batchMsg.MsgIndex,
   103  						"depositor", batchMsg.Msg.GetDepositor(),
   104  						"error", err)
   105  					if err := k.RefundDeposit(ctx, batchMsg, poolBatch); err != nil {
   106  						panic(err)
   107  					}
   108  				}
   109  				return false
   110  			})
   111  
   112  			k.IterateAllPoolBatchWithdrawMsgStates(ctx, poolBatch, func(batchMsg types.WithdrawMsgState) bool {
   113  				if batchMsg.Executed || batchMsg.ToBeDeleted || batchMsg.Succeeded {
   114  					return false
   115  				}
   116  				executedMsgCount++
   117  				if err := k.ExecuteWithdrawal(ctx, batchMsg, poolBatch); err != nil {
   118  					logger.Error("withdraw failed",
   119  						"poolID", poolBatch.PoolId,
   120  						"batchIndex", poolBatch.Index,
   121  						"msgIndex", batchMsg.MsgIndex,
   122  						"withdrawer", batchMsg.Msg.GetWithdrawer(),
   123  						"error", err)
   124  					if err := k.RefundWithdrawal(ctx, batchMsg, poolBatch); err != nil {
   125  						panic(err)
   126  					}
   127  				}
   128  				return false
   129  			})
   130  
   131  			// Mark the batch as executed when any msgs were executed.
   132  			if executedMsgCount > 0 {
   133  				poolBatch.Executed = true
   134  				k.SetPoolBatch(ctx, poolBatch)
   135  			}
   136  		}
   137  		return false
   138  	})
   139  }
   140  
   141  // HoldEscrow sends coins to the module account for an escrow.
   142  func (k Keeper) HoldEscrow(ctx sdk.Context, depositor sdk.AccAddress, depositCoins sdk.Coins) error {
   143  	if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, depositor, types.ModuleName, depositCoins); err != nil {
   144  		return err
   145  	}
   146  	return nil
   147  }
   148  
   149  // If batch messages have expired or have not been processed, coins that were deposited with this function are refunded to the escrow.
   150  func (k Keeper) ReleaseEscrow(ctx sdk.Context, withdrawer sdk.AccAddress, withdrawCoins sdk.Coins) error {
   151  	if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawer, withdrawCoins); err != nil {
   152  		return err
   153  	}
   154  	return nil
   155  }
   156  
   157  // Generate inputs and outputs to treat escrow refunds atomically.
   158  func (k Keeper) ReleaseEscrowForMultiSend(withdrawer sdk.AccAddress, withdrawCoins sdk.Coins) (
   159  	banktypes.Input, banktypes.Output, error) {
   160  	var input banktypes.Input
   161  	var output banktypes.Output
   162  
   163  	input = banktypes.NewInput(k.accountKeeper.GetModuleAddress(types.ModuleName), withdrawCoins)
   164  	output = banktypes.NewOutput(withdrawer, withdrawCoins)
   165  
   166  	if err := banktypes.ValidateInputsOutputs([]banktypes.Input{input}, []banktypes.Output{output}); err != nil {
   167  		return banktypes.Input{}, banktypes.Output{}, err
   168  	}
   169  
   170  	return input, output, nil
   171  }
   172  
   173  // In order to deal with the batch at the same time, the coins of msgs are deposited in escrow.
   174  func (k Keeper) DepositWithinBatch(ctx sdk.Context, msg *types.MsgDepositWithinBatch) (types.DepositMsgState, error) {
   175  	if err := k.ValidateMsgDepositWithinBatch(ctx, *msg); err != nil {
   176  		return types.DepositMsgState{}, err
   177  	}
   178  
   179  	poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
   180  	if !found {
   181  		return types.DepositMsgState{}, types.ErrPoolBatchNotExists
   182  	}
   183  
   184  	if poolBatch.BeginHeight == 0 {
   185  		poolBatch.BeginHeight = ctx.BlockHeight()
   186  	}
   187  
   188  	msgState := types.DepositMsgState{
   189  		MsgHeight: ctx.BlockHeight(),
   190  		MsgIndex:  poolBatch.DepositMsgIndex,
   191  		Msg:       msg,
   192  	}
   193  
   194  	if err := k.HoldEscrow(ctx, msg.GetDepositor(), msg.DepositCoins); err != nil {
   195  		return types.DepositMsgState{}, err
   196  	}
   197  
   198  	poolBatch.DepositMsgIndex++
   199  	k.SetPoolBatch(ctx, poolBatch)
   200  	k.SetPoolBatchDepositMsgState(ctx, poolBatch.PoolId, msgState)
   201  
   202  	return msgState, nil
   203  }
   204  
   205  // In order to deal with the batch at the same time, the coins of msgs are deposited in escrow.
   206  func (k Keeper) WithdrawWithinBatch(ctx sdk.Context, msg *types.MsgWithdrawWithinBatch) (types.WithdrawMsgState, error) {
   207  	if err := k.ValidateMsgWithdrawWithinBatch(ctx, *msg); err != nil {
   208  		return types.WithdrawMsgState{}, err
   209  	}
   210  
   211  	poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
   212  	if !found {
   213  		return types.WithdrawMsgState{}, types.ErrPoolBatchNotExists
   214  	}
   215  
   216  	if poolBatch.BeginHeight == 0 {
   217  		poolBatch.BeginHeight = ctx.BlockHeight()
   218  	}
   219  
   220  	batchPoolMsg := types.WithdrawMsgState{
   221  		MsgHeight: ctx.BlockHeight(),
   222  		MsgIndex:  poolBatch.WithdrawMsgIndex,
   223  		Msg:       msg,
   224  	}
   225  
   226  	if err := k.HoldEscrow(ctx, msg.GetWithdrawer(), sdk.NewCoins(msg.PoolCoin)); err != nil {
   227  		return types.WithdrawMsgState{}, err
   228  	}
   229  
   230  	poolBatch.WithdrawMsgIndex++
   231  	k.SetPoolBatch(ctx, poolBatch)
   232  	k.SetPoolBatchWithdrawMsgState(ctx, poolBatch.PoolId, batchPoolMsg)
   233  
   234  	return batchPoolMsg, nil
   235  }
   236  
   237  // In order to deal with the batch at the same time, the coins of msgs are deposited in escrow.
   238  func (k Keeper) SwapWithinBatch(ctx sdk.Context, msg *types.MsgSwapWithinBatch, orderExpirySpanHeight int64) (*types.SwapMsgState, error) {
   239  	pool, found := k.GetPool(ctx, msg.PoolId)
   240  	if !found {
   241  		return nil, types.ErrPoolNotExists
   242  	}
   243  	if k.IsDepletedPool(ctx, pool) {
   244  		return nil, types.ErrDepletedPool
   245  	}
   246  	if err := k.ValidateMsgSwapWithinBatch(ctx, *msg, pool); err != nil {
   247  		return nil, err
   248  	}
   249  	poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
   250  	if !found {
   251  		return nil, types.ErrPoolBatchNotExists
   252  	}
   253  
   254  	if poolBatch.BeginHeight == 0 {
   255  		poolBatch.BeginHeight = ctx.BlockHeight()
   256  	}
   257  
   258  	currentHeight := ctx.BlockHeight()
   259  
   260  	if orderExpirySpanHeight == 0 {
   261  		params := k.GetParams(ctx)
   262  		u := int64(params.UnitBatchHeight)
   263  		orderExpirySpanHeight = (u - currentHeight%u) % u
   264  	}
   265  
   266  	batchPoolMsg := types.SwapMsgState{
   267  		MsgHeight:            currentHeight,
   268  		MsgIndex:             poolBatch.SwapMsgIndex,
   269  		Executed:             false,
   270  		Succeeded:            false,
   271  		ToBeDeleted:          false,
   272  		OrderExpiryHeight:    currentHeight + orderExpirySpanHeight,
   273  		ExchangedOfferCoin:   sdk.NewCoin(msg.OfferCoin.Denom, sdk.ZeroInt()),
   274  		RemainingOfferCoin:   msg.OfferCoin,
   275  		ReservedOfferCoinFee: msg.OfferCoinFee,
   276  		Msg:                  msg,
   277  	}
   278  
   279  	if err := k.HoldEscrow(ctx, msg.GetSwapRequester(), sdk.NewCoins(msg.OfferCoin.Add(msg.OfferCoinFee))); err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	poolBatch.SwapMsgIndex++
   284  	k.SetPoolBatch(ctx, poolBatch)
   285  	k.SetPoolBatchSwapMsgState(ctx, poolBatch.PoolId, batchPoolMsg)
   286  
   287  	return &batchPoolMsg, nil
   288  }