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 }