emergencyClose()
may fail to repay any debtHigh Risk
https://github.com/Cyfrin/2023-10-SteadeFi/blob/main/contracts/strategy/gmx/GMXEmergency.sol#L111
emergencyClose()
function may become ineffective, preventing the contract from repaying any outstanding debt, leading to potential financial losses.tokenA
and tokenB
).emergencyClose()
function is called after the contract is paused due some reasons, possibly when the strategy incurs bad debts or when the contract gets hacked, High volatility, and so on...tokenA
and tokenB
borrowed from the lendingVault
contract. It then sets the contract's status to closed
. After that, users who hold svToken
shares can withdraw the remaining assets from the contract. function emergencyClose(GMXTypes.Store storage self, uint256 deadline) external {
// Revert if the status is Paused.
GMXChecks.beforeEmergencyCloseChecks(self);
// Repay all borrowed assets; 1e18 == 100% shareRatio to repay
GMXTypes.RepayParams memory _rp;
>> (_rp.repayTokenAAmt, _rp.repayTokenBAmt) = GMXManager.calcRepay(self, 1e18);
(bool _swapNeeded, address _tokenFrom, address _tokenTo, uint256 _tokenToAmt) =
GMXManager.calcSwapForRepay(self, _rp);
if (_swapNeeded) {
ISwap.SwapParams memory _sp;
_sp.tokenIn = _tokenFrom;
_sp.tokenOut = _tokenTo;
_sp.amountIn = IERC20(_tokenFrom).balanceOf(address(this));
_sp.amountOut = _tokenToAmt;
_sp.slippage = self.minSlippage;
_sp.deadline = deadline;
GMXManager.swapTokensForExactTokens(self, _sp);
}
GMXManager.repay(self, _rp.repayTokenAAmt, _rp.repayTokenBAmt);
self.status = GMXTypes.Status.Closed;
emit EmergencyClose(_rp.repayTokenAAmt, _rp.repayTokenBAmt);
}
}
_rp.repayTokenAAmt
and _rp.repayTokenBAmt
represent the entire debt, and these values remain the same even if a swap is needed. function calcSwapForRepay(GMXTypes.Store storage self, GMXTypes.RepayParams memory rp)
external
view
returns (bool, address, address, uint256)
{
address _tokenFrom;
address _tokenTo;
uint256 _tokenToAmt;
if (rp.repayTokenAAmt > self.tokenA.balanceOf(address(this))) {
// If more tokenA is needed for repayment
_tokenToAmt = rp.repayTokenAAmt - self.tokenA.balanceOf(address(this));
_tokenFrom = address(self.tokenB);
_tokenTo = address(self.tokenA);
return (true, _tokenFrom, _tokenTo, _tokenToAmt);
} else if (rp.repayTokenBAmt > self.tokenB.balanceOf(address(this))) {
// If more tokenB is needed for repayment
_tokenToAmt = rp.repayTokenBAmt - self.tokenB.balanceOf(address(this));
_tokenFrom = address(self.tokenA);
_tokenTo = address(self.tokenB);
return (true, _tokenFrom, _tokenTo, _tokenToAmt);
} else {
// If there is enough to repay both tokens
return (false, address(0), address(0), 0);
}
}
tokenA
) is insufficient to cover tokenA
debt, it means that the contract balance of the other token (tokenB
) should be greater than the debt of tokenB
, and the value of the remaining balance of tokenB
after paying off the tokenB
debt should be equal or greater than the required value to cover the debt of tokenA
The two main issues with this assumption are:
tokenFrom
is not enough to be swapped for _tokenToAmt
of tokenTo
, the swap will revert, causing the function to revert each time it is called when the balance of tokenFrom
is insufficient.(in most cases in delta long strategy since it's only borrow one token), This is highly likely since emergency closures occur when something detrimental has happened, (such as bad debts).tokenFrom
(EX: tokenA
) becomes less than _rp.repayTokenAAmt
after a swap. In this case, the repay
call will revert when the lendingVault
contract attempts to transferFrom
the strategy contract for an amount greater than its balance. ex :
in value
), the emergencyClose()
function will always revert, preventing any debt repayment.pause
action and the emergency close
action. During periods of high volatility, the pause action temporarily halts the contract, but the prices of the two assets may continue to decline. The emergency close function can only be triggered by the owner, who operates a time-lock wallet. In the time between the pause and close actions, the prices may drop significantly and this condition will met since the swap
is needed in almost all cases.emergencyClose()
function will consistently fail to repay any debt.vs code manual review
pause
action. and in case of resume
just re-borrow again.