Summary
The SmartVault contract has a function that allows the user to perform swaps with amountOutMinimum = 0.
Vulnerability Details
The SmartVaultV3::swap() allows users to swaps the assets the vault has for another asset.
https://github.com/Cyfrin/2023-12-the-standard/blob/91132936cb09ef9bf82f38ab1106346e2ad60f91/contracts/SmartVaultV3.sol#L222-L232
@> 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
});
The amountOutMinimum variable of the swap is calculated through the calculateMinimumAmountOut(). This function makes sure that there will be enough collateral to cover the current minted position even after the swap.
https://github.com/Cyfrin/2023-12-the-standard/blob/91132936cb09ef9bf82f38ab1106346e2ad60f91/contracts/SmartVaultV3.sol#L213-L214
uint256 requiredCollateralValue = minted * _manager.collateralRate() / _manager.HUNDRED_PC();
uint256 collateralValueMinusSwapValue = euroCollateral() - calculator.tokenToEur(getToken(_inTokenSymbol), _amount);
In the event the swapped amount doesn't impact the collaterisation rate of the loan, the calculateMinimumAmountOut() function will return 0. which means that the swap will be executed without slippage.
https://github.com/Cyfrin/2023-12-the-standard/blob/91132936cb09ef9bf82f38ab1106346e2ad60f91/contracts/SmartVaultV3.sol#L215-L216
return collateralValueMinusSwapValue >= requiredCollateralValue ?
@> 0 : calculator.eurToToken(getToken(_outTokenSymbol), requiredCollateralValue - collateralValueMinusSwapValue);
Impact
The swapped funds of the borrower can be lost as they are exposed to a 0 slippage swap.
Tools Used
manual review
Recommendations
Allow the user to set slippage if the position remains. This logic below will fix the function.
function calculateMinimumAmountOut(bytes32 _inTokenSymbol, bytes32 _outTokenSymbol, uint256 _amount, uint256 minOut) private view returns (uint256) {
ISmartVaultManagerV3 _manager = ISmartVaultManagerV3(manager);
uint256 requiredCollateralValue = minted * _manager.collateralRate() / _manager.HUNDRED_PC();
uint256 collateralValueMinusSwapValue = euroCollateral() - calculator.tokenToEur(getToken(_inTokenSymbol), _amount);
uint256 minRequiredOut = calculator.eurToToken(getToken(_outTokenSymbol), requiredCollateralValue - collateralValueMinusSwapValue)
return collateralValueMinusSwapValue >= requiredCollateralValue ?
@> minOut : getMax(minRequiredOut, minOut) ;
}
function getMax(uint256 var1, uint256 var2) public pure returns (uint256) {
if (var1 >= var2) {
return var1;
} else {
return var2;
}
}