Summary
The _doDexSwap
function currently lacks slippage protection, which can lead to potential issues where the output
amount from the swap is significantly lower than expected. This can result in substantial losses for users.
Vulnerability Details
Current implementation:
function _doDexSwap(bytes memory data, bool isCollateralToIndex) internal returns (uint256 outputAmount) {
(address to, uint256 amount, bytes memory callData) = abi.decode(data, (address, uint256, bytes));
IERC20 inputToken;
IERC20 outputToken;
if (isCollateralToIndex) {
inputToken = collateralToken;
outputToken = IERC20(indexToken);
} else {
inputToken = IERC20(indexToken);
outputToken = collateralToken;
}
uint256 balBefore = outputToken.balanceOf(address(this));
ParaSwapUtils.swap(to, callData);
outputAmount = IERC20(outputToken).balanceOf(address(this)) - balBefore;
emit DexSwap(address(inputToken), amount, address(outputToken), outputAmount, isCollateralToIndex);
}
Issues:
No check to ensure output amount meets a minimum threshold
No slippage protection to prevent significant losses during swaps
Potential for significant value loss if the swap rate changes drastically
Impact
MEDIUM severity because:
Users can suffer significant losses due to unfavorable swap rates
Lack of slippage protection can lead to unexpected and undesirable outcomes
Compromises the integrity and trustworthiness of the protocol
Recommendations
Add slippage protection to _doDexSwap
:
function _doDexSwap(bytes memory data, bool isCollateralToIndex, uint256 minOutputAmount) internal returns (uint256 outputAmount) {
(address to, uint256 amount, bytes memory callData) = abi.decode(data, (address, uint256, bytes));
IERC20 inputToken;
IERC20 outputToken;
if (isCollateralToIndex) {
inputToken = collateralToken;
outputToken = IERC20(indexToken);
} else {
inputToken = IERC20(indexToken);
outputToken = collateralToken;
}
uint256 balBefore = outputToken.balanceOf(address(this));
ParaSwapUtils.swap(to, callData);
outputAmount = IERC20(outputToken).balanceOf(address(this)) - balBefore;
require(outputAmount > 0, "Output amount must be greater than zero");
require(outputAmount >= minOutputAmount, "Slippage protection: output amount is less than minimum required");
emit DexSwap(address(inputToken), amount, address(outputToken), outputAmount, isCollateralToIndex);
}
Update _runSwap
to include minOutputAmount
parameter:
function _runSwap(bytes[] memory metadata, bool isCollateralToIndex, MarketPrices memory prices, uint256 minOutputAmount)
internal
returns (bool completed)
{
if (metadata.length == 0) {
revert Error.InvalidData();
}
if (metadata.length == 2) {
(PROTOCOL _protocol, bytes memory data) = abi.decode(metadata[0], (PROTOCOL, bytes));
if (_protocol != PROTOCOL.DEX) {
revert Error.InvalidData();
}
swapProgressData.swapped = swapProgressData.swapped + _doDexSwap(data, isCollateralToIndex, minOutputAmount);
(_protocol, data) = abi.decode(metadata[1], (PROTOCOL, bytes));
if (_protocol != PROTOCOL.GMX) {
revert Error.InvalidData();
}
_doGmxSwap(data, isCollateralToIndex);
return false;
} else {
if (metadata.length != 1) {
revert Error.InvalidData();
}
(PROTOCOL _protocol, bytes memory data) = abi.decode(metadata[0], (PROTOCOL, bytes));
if (_protocol == PROTOCOL.DEX) {
uint256 outputAmount = _doDexSwap(data, isCollateralToIndex, minOutputAmount);
if (flow == FLOW.DEPOSIT) {
_mint(counter, outputAmount + swapProgressData.swapped, true, prices);
} else if (flow == FLOW.WITHDRAW) {
_handleReturn(outputAmount + swapProgressData.swapped, false, true);
} else {
_updateState(!isCollateralToIndex, isCollateralToIndex);
}
return true;
} else {
_doGmxSwap(data, isCollateralToIndex);
return false;
}
}
}
This ensures that the minOutputAmount
parameter is passed along and used in _doDexSwap
to provide slippage
protection.