15,000 USDC
View results
Submission Details
Severity: medium
Valid

Decimals of Chainlink price feeds should not be assumed

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: // price of ETH (token)
342: // $/ETH ETH ??
343: // $2000 / ETH. $1000 = 0.5 ETH
344: AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
345: (, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
346: // ($10e18 * 1e18) / ($2000e8 * 1e10)
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: // 1 ETH = $1000
365: // The returned value from CL will be 1000 * 1e8
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;
}

Support

FAQs

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