Part 2

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

Adapter's slippage check is not sufficient because it calculates `amountOutMinimum` from token prices in same transaction.

Summary

Adapter's slippage check is not sufficient because it calculates amountOutMinimum from token prices in same transaction.

Vulnerability Details

CurveAdapter.sol#executeSwapExactInputSingle() function is as follows.

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
});
}

As we can see above, expectedAmountOut is calculated from BaseAdapter.sol#getExpectedOutput().

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();
}

From above code, we can see that exectedAmountOut is calculated from prices.

And then amountOutMinimum is calculated from this value.
This can cause some problems.

A transaction can be delayed for much time because of some reasons such as gas price.
Then, token prices can be changed more than expected.
In this case, caller will lose funds more than expected.

And difference between token price and pool's current tick will increase probability of DOS.

This problem exists in CurveAdapter, UniswapV2Adapter and UniswapV3Adapter.

Impact

A transaction can be delayed for much time because of some reasons such as gas price.
Then, token prices can be changed more than expected.
In this case, caller will lose funds more than expected.

And difference between token price and pool's current tick will increase probability of DOS.

Tools Used

Manual review

Recommendations

  1. Add amountOutMinimum variable to SwapExactInputSinglePayload and SwapExactInputPayload struct.

  2. Modify logics to check slippage with this amountOutMinimum.

Updates

Lead Judging Commences

inallhonesty Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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