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

Functionality to gather collateral USD value fails if any collateral doesn't use 18 decimals of precision

Summary

The USD pricing of collateral breaks if any of the collateral tokens don't use 18 decimals of precision. For example any collateral which has < 18 decimals of precision will be severely undervalued in USD terms. This is because the getUsdValue function does not convert the USD value of the amount of collateral token to 18 decimals of precision, which matches the precision of the DSC token.

Vulnerability Details

The getUsdValue is called to return the USD value of any collateral token and is defined as follows:

function getUsdValue(address token, uint256 amount) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
}

To gather the total collateral value in USD for a user, the getAccountCollateralValue is then run:

function getAccountCollateralValue(address user) public view returns (uint256 totalCollateralValueInUsd) {
for (uint256 i = 0; i < s_collateralTokens.length; i++) {
address token = s_collateralTokens[i];
uint256 amount = s_collateralDeposited[user][token];
totalCollateralValueInUsd += getUsdValue(token, amount);
}
return totalCollateralValueInUsd;
}

Let's consider the case where we have two collateral tokens: USDT with 6 decimals of precision, and wETH which has 18 decimals of precision. In the getUsdValue function, the amount of token will be using the native precision of the underlying collateral token (e.g. 1e6 for USDT and 1e18 for wETH). Since we know the precision of USD prices from chainlink is 1e8, we can calculate the precision for each token as returned by getUsdValue:

  1. USDT: ((1e8 * 1e10) * 1e6) / 1e18 = 1e6

  2. wETH: ((1e8 * 1e10) * 1e18) / 1e18 = 1e18

As we can see, the precision of the USD price returned for each asset differs, and so when they are added together in the getAccountCollateralValue, the value of the USDT collateral for the user is being severely undervalued.

Impact

All collateral which has < 18 decimals of precision will be undervalued in USD terms, while all collateral with > 18 decimals of precision will be overvalued in USD terms. This breaks the entire protocol design.

Tools Used

Manual review

Recommendations

Assuming the case in which precision of collateral is constrained to <= 18 decimals & we assume all collateral supports the decimals function, we can make the following change in the getUsdValue function:

return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * (amount * 1**(18-IERC20Metadata(token).decimals()))) / PRECISION;

Support

FAQs

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