withdraw_failed
status if the deposit to GMX
get CancelledHigh Risk
https://github.com/Cyfrin/2023-10-SteadeFi/blob/main/contracts/strategy/gmx/GMXWithdraw.sol
GMX
get canceled after a failed withdrawal, the contract stuck in the withdraw_failed
status.withdraw_failed
is set when the vault successfully withdrew from GMX, but the callback failed during the processWithdraw()
checks inside the try
call, as seen here: function processWithdraw(GMXTypes.Store storage self) external {
GMXChecks.beforeProcessWithdrawChecks(self);// revert if status not withdraw
try GMXProcessWithdraw.processWithdraw(self) {
////... some code
} catch (bytes memory reason) {
// withdraw failed only can be set here .
>> self.status = GMXTypes.Status.Withdraw_Failed;
emit WithdrawFailed(reason);
}
}
the keeper listens to events in this scenario, it calls the processWithdrawFailure() function. This function reborrows the same amount's :
function processWithdrawFailure(GMXTypes.Store storage self, uint256 slippage, uint256 executionFee) external {
GMXChecks.beforeProcessAfterWithdrawFailureChecks(self);
// Re-borrow assets based on the repaid amount
>> GMXManager.borrow(
self, self.withdrawCache.repayParams.repayTokenAAmt, self.withdrawCache.repayParams.repayTokenBAmt
);
//......
}
it then add liquidity to gmx
:
function processWithdrawFailure(GMXTypes.Store storage self, uint256 slippage, uint256 executionFee) external {
GMXChecks.beforeProcessAfterWithdrawFailureChecks(self);
// Re-borrow assets based on the repaid amount
GMXManager.borrow(
self, self.withdrawCache.repayParams.repayTokenAAmt, self.withdrawCache.repayParams.repayTokenBAmt
);
// .... some code
// Re-add liquidity with all tokenA/tokenB in vault
>> self.withdrawCache.depositKey = GMXManager.addLiquidity(self, _alp);
}
The problem arises when adding liquidity to GMX
is canceled, there is no mechanism to handle this scenario when the status is withdraw_failed
. In this case, the callback will revert, as seen here, leaving the tokens from the first withdrawal + borrowed tokens stuck in the contract with the withdraw_failed
status.
In this situation, the only available action to interact with the contract is to call processWithdrawFailure() again (or emergencyPause).
Even if the keeper can call this without any event listening, doing so exacerbates the situation. It results in a loop of
borrow more => add liquidity => get canceled
, continuing until there are no more funds to borrow or the keeper runs out of gas.
capacity
before borrowing. This results in repeated reverting transactions since the amount the keeper want to borrow is more then the amount available in the lending
contract.vs code.
afterWithdrawalCancellation()
function of the callback
contract, implement proper handling for canceled liquidity addition when the status is withdraw_Failed
.