Assessment reports>Concrete>Medium findings>Incorrect swap logic in ,_protocolWithdraw
Category: Coding Mistakes

Incorrect swap logic in _protocolWithdraw

Medium Impact
Medium Severity
High Likelihood

Description

The _protocolWithdraw function in MorphoVaultStrategy contains two issues in its background swap logic:

function _protocolWithdraw(uint256 assets_, uint256) internal virtual override {
	// [...]
	if (isBackgroundSwapEnabled) {
		uint256 expectedOutput = UniswapV3HelperV1.getExpectedOutput(
			IQuoterV2(uniswapQuoter), _asset, address(_backgroundSwap), assets_, poolFee
		);
		isValidQuote(_asset, address(_backgroundSwap), assets_, expectedOutput);
		//@dev add slippage protection and increase the amount to withdraw by 1%
		uint256 swapAssets = expectedOutput.mulDiv(100_00 + MAX_SLIPPAGE, 100_00, Math.Rounding.Floor);
		uint256 _totalAssets_ = _morphoVault.convertToAssets(_morphoVault.balanceOf(address(this)));
		uint256 assetsToWithdraw = swapAssets > _totalAssets_ ? _totalAssets_ : swapAssets;
		//slither-disable-next-line unused-return
		_morphoVault.withdraw(assetsToWithdraw, address(this), address(this));
		uint256 swappedAmount = _swapExactTokenToToken(address(_backgroundSwap), _asset, assetsToWithdraw, 0);
		emit BackgroundSwapWithdraw(assetsToWithdraw, swappedAmount);
	} else {
		// [...]
	}
}

First, UniswapV3HelperV1.getExpectedOutput calls quoterV2.quoteExactInputSingle to calculate output tokens (_backgroundSwap) received for assets_ input tokens (_asset):

function getExpectedOutput(IQuoterV2 quoterV2, address tokenIn, address tokenOut, uint256 amountIn, uint24 poolFee)
    external
    returns (uint256 amountOut)
{
    IQuoterV2.QuoteExactInputSingleParams memory quoteExactInputSingleParams = IQuoterV2.QuoteExactInputSingleParams({
        tokenIn: tokenIn,
        tokenOut: tokenOut,
        amountIn: amountIn,
        fee: poolFee,
        sqrtPriceLimitX96: 0
    });
    validateFeeTier(poolFee);
    (amountOut,,,) = quoterV2.quoteExactInputSingle(quoteExactInputSingleParams);
}

However, the intended behavior in _protocolWithdraw requires calculating input tokens (_backgroundSwap) needed to obtain assets_ amount of output tokens (_asset). In this case, quoterV2.quoteExactOutputSingle instead of quoterV2.quoteExactInputSingle should be used.

Second, _protocolWithdraw adds MAX_SLIPPAGE percentage to the input token amount:

uint256 swapAssets = expectedOutput.mulDiv(100_00 + MAX_SLIPPAGE, 100_00, Math.Rounding.Floor);

This addition is unnecessary. The quote simulates the actual swap, and both operations occur in the same transaction. No slippage occurs between simulation and execution. Instead of withdrawing extra tokens, the function should specify minAmountOut in _swapExactTokenToToken:

-uint256 swappedAmount = _swapExactTokenToToken(address(_backgroundSwap), _asset, assetsToWithdraw, 0);
+uint256 swappedAmount = _swapExactTokenToToken(address(_backgroundSwap), _asset, assetsToWithdraw, assets_);

Impact

Each background swap in _protocolWithdraw withdraws an extra MAX_SLIPPAGE percentage from _morphoVault. These excess tokens remain idle in the strategy, reducing yield potential.

Recommendations

First, add a getExpectedInput function to UniswapV3HelperV1 that uses quoterV2.quoteExactOutputSingle to calculate required input tokens for a specific output amount. Replace getExpectedOutput with this function in _protocolWithdraw.

Second, execute _swapExactTokenToToken with minAmountOut set to assets_, and also add an isValidQuote check to ensure the swap price is within a valid range.

-uint256 swappedAmount = _swapExactTokenToToken(address(_backgroundSwap), _asset, assetsToWithdraw, 0);
+isValidQuote(address(_backgroundSwap), _asset, assetsToWithdraw, assets_);
+uint256 swappedAmount = _swapExactTokenToToken(address(_backgroundSwap), _asset, assetsToWithdraw, assets_);

Remediation

This issue has been acknowledged by Blueprint Finance, and a fix was implemented in commit 0a705658.

Zellic © 2025Back to top ↑