Part 2

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

High MIN_SLIPPAGE_BPS in Curve stable swaps allows attacker to exploit price manipulation

Summary

Minimum slippage allowed in BaseAdapter is MIN_SLIPPAGE_BPS=100 which is high for the Curve stable swaps where usual slippage tolerance equals to 0.1%. This allows attacker to frontrun swap function in the protocol, manipulate the Curve pool balance, and profit from the swaps, while protocol will take less tokens than he should.

Vulnerability Details

Minimum slippage allowed is set to 100 in Constants.sol:34:

/// @notice minimum allowed slippage tolerance
uint256 internal constant MIN_SLIPPAGE_BPS = 100;

Contract calculates minimum amount out depending on Chainlink price and slippageToleranceBps which can be >=MIN_SLIPPAGE_BPS:

/// @notice Get the expected output amount
/// @param tokenIn The token in address
/// @param tokenOut The token out address
/// @param amountIn The input amount in native precision of tokenIn
/// @return expectedAmountOut The expected amount out in native precision of tokenOut
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();
}
/// @notice Calculate the amount out min
/// @param amountOutMinExpected The amount out min expected
/// @return amountOutMin The amount out min
function calculateAmountOutMin(uint256 amountOutMinExpected) public view returns (uint256 amountOutMin) {
// calculate the amount out min
amountOutMin =
(amountOutMinExpected * (Constants.BPS_DENOMINATOR - slippageToleranceBps)) / Constants.BPS_DENOMINATOR;
}
/// @notice Sets slippage tolerance
function setSlippageTolerance(uint256 newSlippageTolerance) public onlyOwner {
// enforce min/max slippage tolerance
if (newSlippageTolerance < Constants.MIN_SLIPPAGE_BPS) {
revert Errors.MinSlippageTolerance(newSlippageTolerance, Constants.MIN_SLIPPAGE_BPS);
}
if (newSlippageTolerance > Constants.MAX_SLIPPAGE_BPS) {
revert Errors.MaxSlippageTolerance(newSlippageTolerance, Constants.MAX_SLIPPAGE_BPS);
}
// update storage
slippageToleranceBps = newSlippageTolerance;
// emit the event LogSetSlippageTolerance
emit LogSetSlippageTolerance(newSlippageTolerance);
}

And because 1% is high slippage for Curve stable swaps, attacker can manipulate price in Curve pool just before protocol will call swap and attacker will take profit while protocol will lose 1% on swap.

PoC

Take for example that protocol wants to do CreditDelegationBranch.sol:rebalanceVaultsAssets() (attacker can easily frontrun this function call because it's called by ChainlinkKeeper when checkUpkeep() returns true).

Pre-conditions:

Keeper wants to do rebalance for in-credit vault by assets in in-debt vault which has collateral USDe.

Swap strategy goes through Curve USDC/USDe pool.

Owner set slippageToleranceBps = 100 (1%) (it's minimum).

Assume Chainlink price 1 USDC = 1 USDe.

Current Curve USDC/USDe Pool reserves on Arbitrum:

USDC - 177_824 USDC - 48.9%

USDe - 185_818 USDe - 51.1%

inDebtVaultCollateralAsset amount to rebalance = 10_000 USDe.

expectedAmountOut = 10_000 USDC because of Chainlink price.

amountOutMinimum = 9900 USDC because minimum price slippage equals to 1%.

In usual scenario contract will get around 9991 USDC which is ok.

Attack:

  1. Attacker frontrun rebalanceVaultsAssets() and imbalance Curve USDC/USDe pool (he will need not much of tokens because pool is small).

  2. Keeper calls rebalanceVaultsAssets() and swaps 10_000 USDe for 9900 USDC.

  3. Attacker takes his tokens and profit back from pool.

  4. Contract instead of 9991 USDC gets 9900 USDC.

Impact

Protocol takes loss on Curve stable swaps equals around 1% (but if minimum slippage will be set higher by owner, loss will be higher). 1% on small amountIn is not much in tokens amount, but if amountIn will be higher, then loss in tokens amount also will be higher.

Tools Used

Manual Review

Recommendations

Consider reducing the minimum slippage tolerance for stable swaps to prevent price manipulation in low-liquidity pools.

Updates

Lead Judging Commences

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

Appeal created

elolpuer Submitter
6 months ago
inallhonesty Lead Judge
6 months ago
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.