Summary
distributeAssets() assumes that all tokens in the protocol will have at most 18 decimals and uses _portion * 10 ** (18 - asset.token.dec) in its calculations. This is not necessarily true and will fail due to underflow whenever token decimals() > 18.
Similar logic error effects the swap() functionality.
LiquidationPool.sol#L220
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
consolidatePendingStakes();
(,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
uint256 stakeTotal = getStakeTotal();
uint256 burnEuros;
uint256 nativePurchased;
for (uint256 j = 0; j < holders.length; j++) {
Position memory _position = positions[holders[j]];
uint256 _positionStake = stake(_position);
if (_positionStake > 0) {
for (uint256 i = 0; i < _assets.length; i++) {
ILiquidationPoolManager.Asset memory asset = _assets[i];
if (asset.amount > 0) {
(,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;
@------------------> uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
* _hundredPC / _collateralRate;
...
...
}
SmartVaultV3.sol#L217
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);
}
Vulnerability Details
The above mentioned code will revert for such tokens which have decimals() greater than 18. This issue is also visible in contracts/utils/PriceCalculator.sol#L40 inside the function getTokenScaleDiff() which is in turn called by functions
Impact
Critical functions of the protocol like swap(), distributeAssets() do not function for tokens with decimals > 18.
Tools Used
Manual inspection.
Recommendations
This would require changes in multiple places. Consider operating with a higher precision like 36 decimals instead of the current 18, wherein every token is converted to 36 places of decimal precision and then converted back to original scale when required.