Summary
LiquidationPool::distributeAssets assumes all of the returned USD Chainlink prices have 8 decimals but there are tokens whose USD feeds have different decimals to 8.
Vulnerability Details
Let's assume dec = 18 priceEurUsd & assetPriceUsd could be of two different decimals other than the assumed 8 decimals by the protocol for such price data returned by Chainlink.
For example AMPL / USD is one of such price feeds with 18 decimals.
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
...
(,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
...
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;
if (costInEuros > _position.EUROs) {
_portion = _portion * _position.EUROs / costInEuros;
costInEuros = _position.EUROs;
}
...
}
}
}
positions[holders[j]] = _position;
}
if (burnEuros > 0) IEUROs(EUROs).burn(address(this), burnEuros);
returnUnpurchasedNative(_assets, nativePurchased);
}
Impact
In the case where the assumed returned decimals is not what ends up being true, the calculations will be wrong.
Tools Used
Manual review
Recommendations
Use the decimals() function of the price feed contract instead to get the correct decimals and calculate based on that.
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
...
(,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
+ uint256 eurUsdScaledDecimals = uint256(priceEurUsd) * 10**(18 - Chainlink.AggregatorV3Interface(eurUsd).decimals());
...
if (asset.amount > 0) {
(,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
+ uint256 assetPriceUsdScaled = uint256(assetPriceUsd) * 10**(18 - Chainlink.AggregatorV3Interface(asset.token.clAddr).decimals());
uint256 _portion = asset.amount * _positionStake / stakeTotal;
- uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd) * _hundredPC / _collateralRate;
+ uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsdScaled) / uint256(eurUsdScaledDecimals) * _hundredPC / _collateralRate;
if (costInEuros > _position.EUROs) {
_portion = _portion * _position.EUROs / costInEuros;
costInEuros = _position.EUROs;
}
...
}
}
}
positions[holders[j]] = _position;
}
if (burnEuros > 0) IEUROs(EUROs).burn(address(this), burnEuros);
returnUnpurchasedNative(_assets, nativePurchased);
}