Summary
SmartVaultV3 swaps Collateral through the uniswap V3 and instead of spot price it does computation based on the average price of 4 hours.
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);
}
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/SmartVaultV3.sol#L214
Owner of smart vault calls swap()
to swap input token to output token. To set the slippage it calls private function calculateMinimumAmountOut()
206 function calculateMinimumAmountOut(bytes32 _inTokenSymbol, bytes32 _outTokenSymbol, uint256 _amount) private view returns (uint256) {
207 ISmartVaultManagerV3 _manager = ISmartVaultManagerV3(manager);
208 uint256 requiredCollateralValue = minted * _manager.collateralRate() / _manager.HUNDRED_PC();
209 uint256 collateralValueMinusSwapValue = euroCollateral() - calculator.tokenToEur(getToken(_inTokenSymbol), _amount);
210 return collateralValueMinusSwapValue >= requiredCollateralValue ?
211 0 : calculator.eurToToken(getToken(_outTokenSymbol), requiredCollateralValue - collateralValueMinusSwapValue);
212 }
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/SmartVaultV3.sol#L206
At Line 208 it computes the requiredCollateralValue
which is necessary collateral smart vault should have for minted
euros. but at Line 209 it computes collateralValueMinusSwapValue
from euroCollateral()
which is coming from average token price of 4 hours.
function euroCollateral() private view returns (uint256 euros) {
ITokenManager.Token[] memory acceptedTokens = getTokenManager().getAcceptedTokens();
for (uint256 i = 0; i < acceptedTokens.length; i++) {
ITokenManager.Token memory token = acceptedTokens[i];
euros += calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr));
}
}
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/SmartVaultV3.sol#L71
As here it computes the euros from average price not as spot price
Impact
Not using spot price for swap could leads to too higher/lower slippage i.e. unfavorable trade, which can also trigger liquidation of vault
Tools Used
Manual
Recommendations
Use spot price during swap instead of 4 hours average price of tokens