Vulnerability: Adversary can reenter takeOverDebt() during liquidation to steal vault funds
Severity: high

Description:
First we'll walk through a high level breakdown of the issue to have as context for the rest of the report:\\nCreate a custom token that allows them to take control of the transaction and to prevent liquidation\\nFund UniV3 LP with target token and custom token\\nBorrow against LP with target token as the hold token\\nAfter some time the position become liquidatable\\nBegin liquidating the position via repay()\\nUtilize the custom token during the swap in repay() to gain control of the transaction\\nUse control to reenter into takeOverDebt() since it lack nonReentrant modifier\\nLoan is now open on a secondary address and closed on the initial one\\nTransaction resumes (post swap) on repay()\\nFinish repayment and refund all initial LP\\nPosition is still exists on new address\\nAfter some time the position become liquidatable\\nLoan is liquidated and attacker is paid more LP\\nVault is at a deficit due to refunding LP twice\\nRepeat until the vault is drained of target token\\nLiquidityManager.sol#L279-L287\\n```\\n_v3SwapExactInput(\\n    v3SwapExactInputParams({\\n        fee: params.fee,\\n        tokenIn: cache.holdToken,\\n        tokenOut: cache.saleToken,\\n        amountIn: holdTokenAmountIn,\\n        amountOutMinimum: (saleTokenAmountOut * params.slippageBP1000) /\\n            Constants.BPS\\n    })\\n```\\n\\nThe control transfer happens during the swap to UniV3. Here when the custom token is transferred, it gives control back to the attacker which can be used to call takeOverDebt().\\nLiquidityBorrowingManager.sol#L667-L672\\n```\\n    _removeKeysAndClearStorage(borrowing.borrower, params.borrowingKey, loans);\\n    // Pay a profit to a msg.sender\\n    _pay(borrowing.holdToken, address(this), msg.sender, holdTokenBalance);\\n    _pay(borrowing.saleToken, address(this), msg.sender, saleTokenBalance);\\n\\n    emit Repay(borrowing.borrower, msg.sender, params.borrowingKey);\\n```\\n\\nThe reason the reentrancy works is because the actual borrowing storage state isn't modified until AFTER the control transfer. This means that the position state is fully intact for the takeOverDebt() call, allowing it to seamlessly transfer to another address behaving completely normally. After the repay() call resumes, _removeKeysAndClearStorage is called with the now deleted borrowKey.\\nKeys.sol#L31-L42\\n```\\nfunction removeKey(bytes32[] storage self, bytes32 key) internal {\\n    uint256 length = self.length;\\n    for (uint256 i; i < length; ) {\\n        if (self.unsafeAccess(i).value == key) {\\n            self.unsafeAccess(i).value = self.unsafeAccess(length - 1).value;\\n            self.pop();\\n            break;\\n        }\\n        unchecked {\\n            ++i;\\n        }\\n    }\\n```\\n\\nThe unique characteristic of deleteKey is that it doesn't revert if the key doesn't exist. This allows "removing" keys from an empty array without reverting. This allows the repay call to finish successfully.\\nLiquidityBorrowingManager.sol#L450-L452\\n```\\n    //newBorrowing.accLoanRatePerSeconds = oldBorrowing.accLoanRatePerSeconds;\\n    _pay(oldBorrowing.holdToken, msg.sender, VAULT_ADDRESS, collateralAmt + feesDebt);\\n    emit TakeOverDebt(oldBorrowing.borrower, msg.sender, borrowingKey, newBorrowingKey);\\n```\\n\\nNow we can see how this creates a deficit in the vault. When taking over an existing debt, the user is only required to provide enough hold token to cover any fee debt and any additional collateral to pay fees for the newly transferred position. This means that the user isn't providing any hold token to back existing LP.\\nLiquidityBorrowingManager.sol#L632-L636\\n```\\n        Vault(VAULT_ADDRESS).transferToken(\\n            borrowing.holdToken,\\n            address(this),\\n            borrowing.borrowedAmount + liquidationBonus\\n        );\\n```\\n\\nOn the other hand repay transfers the LP backing funds from the vault. Since the same position is effectively liquidated twice, it will withdraw twice as much hold token as was originally deposited and no new LP funds are added when the position is taken over. This causes a deficit in the vault since other users funds are being withdrawn from the vault.

Impact:
Vault can be drained

Recommended mitigation:
Add the `nonReentrant` modifier to `takeOverDebt()`