The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

calculateMinimumAmountOut() uses flawed logic which will result in no slippage control or wrong minimumAmountOut

Summary

The amountOutMinimum calculated as part of the ISwapRouter.ExactInputSingleParams which should serve as slippage protection will be 0 in certain cases which can lead to catastrophic Sandwich attacks and/or no control over the amount returned from Uniswap.

Vulnerability Details

In the swap function, we're calculating the amountOutMinimum using the calculateMinimumAmountOut() function:

function swap(bytes32 _inToken, bytes32 _outToken, uint256 _amount) external onlyOwner {
uint256 swapFee = _amount * ISmartVaultManagerV3(manager).swapFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
address inToken = getSwapAddressFor(_inToken);
uint256 minimumAmountOut = calculateMinimumAmountOut(_inToken, _outToken, _amount);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: inToken,
tokenOut: getSwapAddressFor(_outToken),
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountIn: _amount - swapFee,
amountOutMinimum: minimumAmountOut,
sqrtPriceLimitX96: 0
});
inToken == ISmartVaultManagerV3(manager).weth() ?
executeNativeSwapAndFee(params, swapFee) :
executeERC20SwapAndFee(params, swapFee);

Proof of Concept:

  • Let's take Bob for example who is the owner of the SmartVault and has deposited 2 ETH to the vault. For the sake of simplifying calculations, let's say that 1 ETH is worth 2,000 EUROs, so total collateral would be worth 4,000 EUROs.

  • This would mean that Bob would be able to mint around 3635 EUROS at a 110% collateralization rate.

  • Bob has minted 500 EUROs from that vault, which at a collateralization of 110% would need 550 EUROs worth of collateral, leaving out 3135 EUROs left for minting (if he decided to mint more EUROs -> at the price of 2K EUROs per 1 ETH, of course).

  • According to the sponsor's information, theswap() function's purpose is to swap between the 5 tokens (ETH, WBTC, ARB, LINK, & PAXG).

  • Bob decides to exchange one of his 1ETH through the swap() function for the amountOut in WBTC, which should equal around 0.052 WBTC.

  • minimumAmountOut:

function calculateMinimumAmountOut(bytes32 _inTokenSymbol, bytes32 _outTokenSymbol, uint256 _amount)
private
view
returns (uint256)
{
ISmartVaultManagerV3 _manager = ISmartVaultManagerV3(manager);
uint256 requiredCollateralValue = minted * _manager.collateralRate() / _manager.HUNDRED_PC();
uint256 collateralValueMinusSwapValue =
euroCollateral() - calculator.tokenToEur(getToken(_inTokenSymbol), _amount);
return collateralValueMinusSwapValue >= requiredCollateralValue
? 0
: calculator.eurToToken(getToken(_outTokenSymbol), requiredCollateralValue - collateralValueMinusSwapValue);
}
  • To calculate the requiredCollateralValue: minted (which is the amount we've minted - 500 EUROs + the minting fee, let's say 505 EUROs in total * 110% collateralization rate = 555 EUROs.

  • collateralValueMinusSwapValue = The total euro collateral in the system that we have is 2 ETH or 4,000 EUROs ( euroCollateral() ) - the amount we want to swap which would be 1 ETH (2,000 EUROs) = 2,000 EUROs

  • return collateralValueMinusSwapValue >= requiredCollateralValue since the if-clause would be true, the output we'd get for minimumAmountOut would be 0.

This would basically mean that the user swapping will have absolutely no slippage protection on that trade.

Impact

The result of the scenario explained above would mean no slippage protection on a certain category of swaps which would allow swaps to be sandwiched leading to catastrophic losses for users, as well as no protection when it comes to any kind of drastic price manipulations/changes in Uniswap Pools that would affect the amountOut.

Tools Used

  • Manual Review

Recommendations

  • Refactor the calculateMinimumAmountOut() function to account for scenarios such as the above one.

Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Slippage-issue

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Slippage-issue

Support

FAQs

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