Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Insufficient Slippage Protection in Adapter due to In-Transaction Price Calculation

Summary

The adapters (CurveAdapter, UniswapV2Adapter, UniswapV3Adapter) calculate minimum output amounts for swaps using token prices obtained within the same transaction, which makes the slippage protection ineffective. This can lead to larger-than-expected losses due to price movements between transaction submission and execution.

Links to affected code

  • BaseAdapter.sol:L95~123

  • CurveAdapter.sol:L75~142

  • UniswapV2Adapter.sol:L75~136

  • UniswapV2Adapter.sol:L81~141

Vulnerability details

Description

The current implementation of slippage protection in the adapter contracts (CurveAdapter, UniswapV2Adapter, and UniswapV3Adapter) has a critical vulnerability in the way it calculates the minimum output amount for swaps.

In CurveAdapter.sol#executeSwapExactInputSingle(), the amountOutMinimum is calculated using token prices obtained within the same transaction:

function executeSwapExactInputSingle(SwapExactInputSinglePayload calldata swapPayload)
external
returns (uint256 amountOut)
{
// transfer the tokenIn from the send to this contract
IERC20(swapPayload.tokenIn).transferFrom(msg.sender, address(this), swapPayload.amountIn);
// approve the tokenIn to the swap router
address curveStrategyRouterCache = curveStrategyRouter;
IERC20(swapPayload.tokenIn).approve(curveStrategyRouterCache, swapPayload.amountIn);
// get the expected output amount
-> uint256 expectedAmountOut = getExpectedOutput(swapPayload.tokenIn, swapPayload.tokenOut, swapPayload.amountIn);
// Calculate the minimum acceptable output based on the slippage tolerance
-> uint256 amountOutMinimum = calculateAmountOutMin(expectedAmountOut);
return ICurveSwapRouter(curveStrategyRouterCache).exchange_with_best_rate({
_from: swapPayload.tokenIn,
_to: swapPayload.tokenOut,
_amount: swapPayload.amountIn,
_expected: amountOutMinimum,
_receiver: swapPayload.recipient
});
}

The issue lies in BaseAdapter.sol#getExpectedOutput() where token prices are used for calculation:

function getExpectedOutput(
address tokenIn,
address tokenOut,
uint256 amountIn
)
public
view
returns (uint256 expectedAmountOut)
{
// fail fast for zero input
if (amountIn == 0) revert Errors.ZeroExpectedSwapOutput();
// get token prices
UD60x18 priceTokenInX18 = IPriceAdapter(swapAssetConfigData[tokenIn].priceAdapter).getPrice();
UD60x18 priceTokenOutX18 = IPriceAdapter(swapAssetConfigData[tokenOut].priceAdapter).getPrice();
// convert input amount from native to internal zaros precision
UD60x18 amountInX18 = Math.convertTokenAmountToUd60x18(swapAssetConfigData[tokenIn].decimals, amountIn);
// calculate the expected amount out in native precision of output token
expectedAmountOut = Math.convertUd60x18ToTokenAmount(
-> swapAssetConfigData[tokenOut].decimals, amountInX18.mul(priceTokenInX18).div(priceTokenOutX18)
);
// revert when calculated expected output is zero; must revert here
// otherwise the subsequent slippage bps calculation will also
// return a minimum swap output of zero giving away the input tokens
if (expectedAmountOut == 0) revert Errors.ZeroExpectedSwapOutput();
}

Impact

  1. Transaction delays due to network congestion or other factors can result in price changes exceeding the expected slippage tolerance.

  2. Users may experience greater losses than anticipated due to price movements between transaction submission and execution.

  3. Discrepancies between oracle prices and actual pool prices increase the likelihood of transaction failures.

Recommended Mitigation Steps

  1. Modify the SwapExactInputSinglePayload and SwapExactInputPayload structs to include an amountOutMinimum parameter.

  2. Update the swap execution logic to use the user-provided amountOutMinimum for slippage protection instead of calculating it from current prices.

Updates

Lead Judging Commences

inallhonesty Lead Judge
5 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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