Steadefi

Steadefi
DeFiHardhatFoundryOracle
35,000 USDC
View results
Submission Details
Severity: medium
Valid

Repaying the lending vault during an emergency situation could lead to all funds being stuck inside the contract

Summary

When calling the emergencyClose function to close a strategy vault, because of an emergency reason. The amount which the strategy vault owes the lending vault is calculated, and this amount is sent to the lending vault. If there is not enough funds left, the function will revert and therefore all funds are stuck inside the contract. Normally it should not be possible that there are less funds left than the strategy vault owes the lending vault, but this is an emergency function, and therefore it should expect the worst.

Vulnerability Details

The emergencyClose function calculates how much the strategy vault owes the lending vault and tries to repay this amount to the lending vault:

function emergencyClose(
GMXTypes.Store storage self,
uint256 deadline
) external {
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
);
}

As we can see in the calcRepay function, it does not take into account the possibility that there are not enough funds in the contract to fully repay the lending vault:

function calcRepay(
GMXTypes.Store storage self,
uint256 shareRatio
) external view returns (uint256, uint256) {
(uint256 tokenADebtAmt, uint256 tokenBDebtAmt) = GMXReader.debtAmt(self);
uint256 _repayTokenAAmt = shareRatio * tokenADebtAmt / SAFE_MULTIPLIER;
uint256 _repayTokenBAmt = shareRatio * tokenBDebtAmt / SAFE_MULTIPLIER;
return (_repayTokenAAmt, _repayTokenBAmt);
}

Therefore, if the emergency reason has led to a situation where the funds in the contract are less than the amount that the strategy vault owes to the lending vault, the attempt to return all funds to the lending vault will revert and therefore a DoS of the emergencyClose function occurs, which means that all funds remain stuck in the contract.

Here we can see the flow of the repay function which leads to a normal safeTransferFrom call without protection against such a revert:

function repay(
GMXTypes.Store storage self,
uint256 repayTokenAAmt,
uint256 repayTokenBAmt
) public {
if (repayTokenAAmt > 0) {
self.tokenALendingVault.repay(repayTokenAAmt);
}
if (repayTokenBAmt > 0) {
self.tokenBLendingVault.repay(repayTokenBAmt);
}
}
function repay(uint256 repayAmt) external nonReentrant {
if (repayAmt == 0) revert Errors.InsufficientRepayAmount();
// Update vault with accrued interest and latest timestamp
_updateVaultWithInterestsAndTimestamp(0);
uint256 maxRepay_ = maxRepay(msg.sender);
if (maxRepay_ > 0) {
if (repayAmt > maxRepay_) {
repayAmt = maxRepay_;
}
// Calculate debt to reduce based on repay amount
uint256 _debt = repayAmt * borrowers[msg.sender].debt / maxRepay_;
// Update vault state
totalBorrows = totalBorrows - repayAmt;
totalBorrowDebt = totalBorrowDebt - _debt;
// Update borrower state
borrowers[msg.sender].debt = borrowers[msg.sender].debt - _debt;
borrowers[msg.sender].lastUpdatedAt = block.timestamp;
// Transfer repay tokens to the vault
asset.safeTransferFrom(msg.sender, address(this), repayAmt);
emit Repay(msg.sender, _debt, repayAmt);
}

Impact

All funds being stuck inside the contract as the emergencyClose function reverts.

Tools Used

Manual Review

Recommendations

If there are not enough funds to pay back the lenders fully than only pay as much as possible and do not try to pay them everything back.

Updates

Lead Judging Commences

hans Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

Not enough token to repay the debt on emergencyClose

Impact: Medium Likelihood: Low The keepers can send tokens directly before closing. Will leave for a sponsor's review but likely to invalidate.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.