The Standard

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

Lack of slippage control in `SmartVaultV3::swap()` when no collateral in vault

Summary

Users face potential slippage risks when interacting with SmartVaultV3::swap() and there are no minted EUROs assets at that moment. This risk arises from a calculation within SmartVaultV3::calculateMinimumAmountOut(), which returns 0 as the accepted slippage control in such cases.

Vulnerability Details

This vulnerability arises from the following function:

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);
}

We can see in the last part that if collateralValueMinusSwapValue is greater than requiredCollateralValue, then 0 will be returned as the minimum amount out. This will be the case if minted is 0 (the vault is not collateralized).

This is then used in SmartVaultV3::swap() and passed to the amountOutMinimum param of UniswapV3, which is where the risky trade happens:

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
});
//...
}

Another way to expose the trader to this risk is if some malicious actor comes in and burns their tokens in someone else's vault. This will cause minted to drop to 0 and the vault owner might not realize it.

If we look at the code for SmartVaultV3::burn():

function burn(uint256 _amount) external ifMinted(_amount) {
uint256 fee = _amount * ISmartVaultManagerV3(manager).burnFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
minted = minted - _amount;
EUROs.burn(msg.sender, _amount);
IERC20(address(EUROs)).safeTransferFrom(msg.sender, ISmartVaultManagerV3(manager).protocol(), fee);
emit EUROsBurned(_amount, fee);
}

We will see that this is indeed the case. This will cost the attacker money and might not be practical to exploit but it's still worth mentioning.

Impact

Users are exposed to potentially dangerous swaps due to a lack of slippage check in this particular case

Tools Used

Manual Analysis

Recommendations

Do not let users execute SmartVaultV3::swap() when the vault is uncollateralized.

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
Validated
Assigned finding tags:

slippage-burn

Support

FAQs

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