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