the emergencyClose()
function may become ineffective, preventing the contract from repaying any outstanding debt, leading to potential financial losses.
When the contract is paused, all the liquidity from GMX is withdrawn (in term of tokenA
and tokenB
).
The 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...
This function is responsible for repaying all the amounts of 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.
The issue with this function lies in its assumptions, which are not accurate. It assumes that the withdrawn amounts from GMX are always sufficient to cover the whole debt.
Please note that _rp.repayTokenAAmt
and _rp.repayTokenBAmt
represent the entire debt, and these values remain the same even if a swap is needed.
The function checks if a swap is needed to cover its debt, and here's how it determines whether a swap is required:
In plain English, this function in this case assumes: if the contract's balance of one of the tokens (e.g., 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:
If the contract balance of 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).
The second issue arises when the balance of 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 :
tokenA balance = 100, debtA = 80.
tokenB balance = 50 , debtB = 70.
after swap tokenA for 20 tokenB .
tokenA balance = 75 , debtA = 80 : in this case repay will keep revert .
so if the contract accumulates bad debts(in value
), the emergencyClose()
function will always revert, preventing any debt repayment.
Another critical factor to consider is the time between the 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.
lenders may lose all their funds
vs code
manual review
the debt need to be repayed in the pause
action. and in case of resume
just re-borrow again.
Impact: Medium Likelihood: Low The keepers can send tokens directly before closing. Will leave for a sponsor's review but likely to invalidate.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.