Summary
Chainlink price feeds are used to fetch the price of tokens in usd. In almost all cases, TOKEN/USD price feeds return price values using 8 decimals, however this is not guaranteed, for example the AMPL/USD has a decimals value of 18. The protocol therefore should not assume that the feed decimals will always be exactly 8.
Vulnerability Details
When handling the returned price from Chainlinks price feeds, PRECISION and ADDITIONAL_FEED_PRECISION are used and have values of 1e18 and 1e10 respectively. The below functions work as expected when the feed decimals is 8, but will return wildly inaccurate results should that not be the case.
File: src\DSCEngine.sol
70: uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;
71: uint256 private constant PRECISION = 1e18;
File: src\DSCEngine.sol
340: function getTokenAmountFromUsd(address token, uint256 usdAmountInWei) public view returns (uint256) {
341:
342:
343:
344: AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
345: (, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
346:
347: return (usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION);
348: }
361: function getUsdValue(address token, uint256 amount) public view returns (uint256) {
362: AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
363: (, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
364:
365:
366: return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
367: }
Impact
If a feed with non-standard decimals is used, the protocol will significantly misprice underlying assets. This can lead to unfair liquidation, or undercollateralization, both of which lead to a loss of user funds.
Tools Used
Manual review
Recommendations
Make a call to priceFeed.decimals() to determine the precision instead of relying on the constant variable ADDITIONAL_FEED_PRECISION.
function getTokenAmountFromUsd(address token, uint256 usdAmountInWei) public view returns (uint256) {
// price of ETH (token)
// $/ETH ETH ??
// $2000 / ETH. $1000 = 0.5 ETH
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
// ($10e18 * 1e18) / ($2000e8 * 1e10)
- return (usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION);
+ uint8 additionalFeedPrecision = 10 ** (18 - priceFeed.decimals());
+ return (usdAmountInWei * PRECISION) / (uint256(price) * (additionalFeedPrecision));
}
function getUsdValue(address token, uint256 amount) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
// 1 ETH = $1000
// The returned value from CL will be 1000 * 1e8
- return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
+ uint8 additionalFeedPrecision = 10 ** (18 - priceFeed.decimals());
+ return ((uint256(price) * additionalFeedPrecision) * amount) / PRECISION;
}