20,000 USDC
View results
Submission Details
Severity: high
Valid

Lack of slippage control puts user funds at risk of MEV sandwich attacks

Summary

Fees#sellProfits executes a swap using Uniswap V3 but fails to implement proper slippage control, leaving funds highly vulnerable to theft through MEV sandwich attacks.

Vulnerability Details

When executing a swap using Uniswap, amountOutMinimum and sqrtPriceLimitX96 are used to define the slippage tolerance of the transaction (see here for more info). amountOutMinimum defines an amount of received tokens that, if fewer are received, the transaction reverts. sqrtPriceLimitX96 determines the price at which to not swap any more tokens.

In Fees#sellProfits, a swap is made using Uniswap V3's SwapRouter, where both amountOutMinimum and sqrtPriceLimitX96 are set to 0. This means that the contract allows up to 100% slippage. In other words, the amount that the contract is able to lose from a sandwich attack is uncapped.

File: src\Fees.sol
24: /// @notice swap loan tokens for collateral tokens from liquidations
25: /// @param _profits the token to swap for WETH
26: function sellProfits(address _profits) public {
27: require(_profits != WETH, "not allowed");
28: uint256 amount = IERC20(_profits).balanceOf(address(this));
29:
30: ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
31: .ExactInputSingleParams({
32: tokenIn: _profits,
33: tokenOut: WETH,
34: fee: 3000,
35: recipient: address(this),
36: deadline: block.timestamp,
37: amountIn: amount,
38: amountOutMinimum: 0, // @audit should be non-zero
39: sqrtPriceLimitX96: 0 // @audit should be non-zero
40: });
41:
42: amount = swapRouter.exactInputSingle(params);
43: IERC20(WETH).transfer(staking, IERC20(WETH).balanceOf(address(this)));
44: }

Impact

Each time sellProfits is called, a considerable amount of user funds are prone to be stolen by MEV bots.

Tools Used

Manual review

Recommendations

Implement non-zero slippage parameters when interacting with Uniswap:

- function sellProfits(address _profits) public {
+ function sellProfits(address _profits, uint256 _amountOutMinimum, uint160 _sqrtPriceLimitX96) public {
require(_profits != WETH, "not allowed");
uint256 amount = IERC20(_profits).balanceOf(address(this));
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: _profits,
tokenOut: WETH,
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountIn: amount,
- amountOutMinimum: 0,
- sqrtPriceLimitX96: 0
+ amountOutMinimum: _amountOutMinimum,
+ sqrtPriceLimitX96: _sqrtPriceLimitX96
});
amount = swapRouter.exactInputSingle(params);
IERC20(WETH).transfer(staking, IERC20(WETH).balanceOf(address(this)));
}

Support

FAQs

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