Summary
swap() is implemented in a manner which states that if after the swap has been executed, the collateral value stil remains at or above the minimum required level then set the minimumAmountOut to zero.
This becomes the amountOutMinimum
of the ISwapRouter.ExactInputSingleParams
and hence there is no slippage protection.
Note that even when the protocol does calculate the minimumAmountOut
as non-zero, it is calculated as just enough to ensure the collateral ratio is maintained. Any slight negative price fluctuations will cause the borrower to become undercollateralized and hence liquidated.
Vulnerability details
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);
}
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);
}
Impact
Unlimited slippage and risk of fund loss for the swapper.
Tools Used
Manual inspection
Recommendations
Allow the user to manually set the amountOutMinimum
. The protocol can still have a logic which checks to make sure this value set by the user is above the value calculated by calculateMinimumAmountOut()
. If not, then the value returned by calculateMinimumAmountOut()
takes precedence.