medium

Fees are hardcoded to 3000 in ExactInputSingleParams

Contest
Reward

Total

42.66 USDC

1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
Selected
1.79 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
1.28 USDC
Selected Submission

Fees are hardcoded to 3000 in ExactInputSingleParams

Severity

Medium Risk

Relevant GitHub Links

https://github.com/Cyfrin/2023-12-the-standard/blob/91132936cb09ef9bf82f38ab1106346e2ad60f91/contracts/SmartVaultV3.sol#L221

Summary

Fixed fee level is used when swap tokens on Uniswap.

Vulnerability Details

In Fees SmartVaultV3::swap a fixed fee 3000 (0.3%) level is used:

    function swap(bytes32 _inToken, bytes32 _outToken, uint256 _amount) external onlyOwner {
        uint256 swapFee = _amount * ISmartVaultManagerV3(manager).swapFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
        address inToken = getSwapAddressFor(_inToken);
        uint256 minimumAmountOut = calculateMinimumAmountOut(_inToken, _outToken, _amount);
        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
                tokenIn: inToken,
                tokenOut: getSwapAddressFor(_outToken),
@>                fee: 3000, //@audit fixed fee
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: _amount - swapFee,
                amountOutMinimum: minimumAmountOut,
                sqrtPriceLimitX96: 0
            });
        inToken == ISmartVaultManagerV3(manager).weth() ?
            executeNativeSwapAndFee(params, swapFee) :
            executeERC20SwapAndFee(params, swapFee);
    }

Usually, there are multiple Uniswap V3 pools available for a given token pair with different swap fees.

For instance, the optimal route to swap USDC for WETH is using the 0.05% (500) swap fee pool, which has significantly more liquidity than the 0.3% (3000) swap fee pool and thus less slippage.

Additionally, if the desired pool is not available, the swap will fail, or an attacker could exploit this by creating an imbalanced pool with the desired swap fee and stealing the tokens.

Impact

The Fees contract uses inefficient swaps, which leads to higher slippage (receiving less WETH) or failing swaps.

Tools Used

Manual review.

Recommendations

It is recommended to Passing fee level to SmartVaultV3::swap function as parameter:

+    function swap(bytes32 _inToken, bytes32 _outToken, uint256 _amount, uint24 _fee) external onlyOwner {
        uint256 swapFee = _amount * ISmartVaultManagerV3(manager).swapFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
        address inToken = getSwapAddressFor(_inToken);
        uint256 minimumAmountOut = calculateMinimumAmountOut(_inToken, _outToken, _amount);
        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
                tokenIn: inToken,
                tokenOut: getSwapAddressFor(_outToken),
+               fee: _fee, //@audit
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: _amount - swapFee,
                amountOutMinimum: minimumAmountOut,
                sqrtPriceLimitX96: 0
            });
        inToken == ISmartVaultManagerV3(manager).weth() ?
            executeNativeSwapAndFee(params, swapFee) :
            executeERC20SwapAndFee(params, swapFee);
    }