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

Collateral decimal != 18 breaks the protocol

Summary

The amount of USD an user can mint is proportional to the value of the deposited collateral. However the protocol assumes all collateral tokens have 18 decimals.
If a token with a different decimals precision is utilized will potentially allow users to mint more than they should or preventing them from using their full collateral value to mint DSC.

Vulnerability Details

DSCEngine.getUsdValue is used to calculate the user collateral value and the amount of USD can be minted. Following formula is used:
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
For WBTC with decimals == 8;
30.000 USD per WBTC;
ADDITIONAL_FEED_PRECISION = 1e10;
PRECISION = 1e18;
amount = 1 WBTC;
we have :
usdValue = 30.000 * 1e8 * 1e10 * 1e8 / 1e18
usdValue = 30.000 * 1e8
However DSC is a 18 decimals tokens. With 1 WBTC collateral, the collateral value will be: 30.000 * 1e8 = 3 * 1e12 < 1 DSC worth of collateral.

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

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L361-L367

Impact

Collateral tokens with decimals precision < 18 will weight less in the user collateral value balance.
When WBTC is used as collateral, the amount of DSC users can mint against it is a few order of magnitude(1e10) smaller than the real deposit value practically making WBTC deposits unusable.

For tokens with decimals precision > 18 is even worse since users can mint more than their deposited collateral value. I didn't found any ERC 20 tokens with decimal precision > 18 and to have a USD chainlink price feed.

Tools Used

Manual review.

Recommendations

  • ensure no collateral with decimal precision > 18 is allowed;

  • Adjust amout in getUsdValue() for decimal precision:

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;
+ return
+ ((uint256(price) * ADDITIONAL_FEED_PRECISION) *
+ amount *
+ 10 ** (18 - ERC20(token).decimals())) / PRECISION;
}
  • Adjust getTokenAmountFromUsd for collateral precision and refactor:

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();
- return
- (usdAmountInWei * PRECISION) /
- (uint256(price) * ADDITIONAL_FEED_PRECISION) /
- 10 ** (18 - ERC20(token).decimals());
+ return
+ (usdAmountInWei * PRECISION * ADDITIONAL_FEED_PRECISION) /
+ uint256(price) /
+ 10 ** (18 - ERC20(token).decimals());
}

Support

FAQs

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