MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: high
Invalid

Inconsistency in `L2TokenReceiver.swap()` Implementation May Lead to Unsuccessful Token Swaps

Summary

The L2TokenReceiver.swap() function utilizes a stored sqrtPriceLimitX96 for each swap, potentially resulting in failed token swaps due to an outdated or out-of-range sqrtPriceLimitX96. The vulnerability arises from passing a previously stored sqrtPriceLimitX96 instead of obtaining the current pool's sqrtPriceLimitX96 for the UniswapV3 pool trade.

Vulnerability Details

function swap(uint256 amountIn_, uint256 amountOutMinimum_) external onlyOwner returns (uint256) {
SwapParams memory params_ = params;
ISwapRouter.ExactInputSingleParams memory swapParams_ = ISwapRouter.ExactInputSingleParams({
tokenIn: params_.tokenIn,
tokenOut: params_.tokenOut,
fee: params_.fee,
recipient: address(this),
deadline: block.timestamp,//@audit deadline
amountIn: amountIn_,
amountOutMinimum: amountOutMinimum_,
sqrtPriceLimitX96: params_.sqrtPriceLimitX96
//@audit same sqrtPriceLimitX96 for different swaps seems inconsistant
});
uint256 amountOut_ = ISwapRouter(router).exactInputSingle(swapParams_);
emit TokensSwapped(params_.tokenIn, params_.tokenOut, amountIn_, amountOut_, amountOutMinimum_);
return amountOut_;
}

https://github.com/Cyfrin/2024-01-Morpheus/blob/main/contracts/L2TokenReceiver.sol#L57

The swap() function swaps the params.tokenIn for params.tokenOut using UniswapV3 pool's exactInputSingle(). Instead of providing the current pool's sqrtPriceLimitX96 for the trade, it passes the params_.sqrtPriceLimitX96 value, which is the previously stored sqrtPriceLimitX96 that doesn't depend on the swap pools condition.

function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override noDelegateCall returns (int256 amount0, int256 amount1) {
require(amountSpecified != 0, 'AS');
Slot0 memory slot0Start = slot0;
require(slot0Start.unlocked, 'LOK');
require(
zeroForOne
? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO
: sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO,
'SPL'
);
...................................
}

https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L608

Additionally, the UniswapV3 swap() function verifies the provided sqrtPriceLimitX96 against the current pool conditions, making it susceptible to variations through flash loan attacks and other methods.

This inconsistency may result in failed swap() transactions or suboptimal execution.

Impact

swap failed

Tools Used

Manual

Recommendations

Consider using the TWAP function to get the value of sqrtPriceX96 .

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
tripathi Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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